diff --git a/external/ai-agents/subagent-driven-development/SKILL.md b/external/ai-agents/subagent-driven-development/SKILL.md index f7bbf6a..4519578 100644 --- a/external/ai-agents/subagent-driven-development/SKILL.md +++ b/external/ai-agents/subagent-driven-development/SKILL.md @@ -70,23 +70,13 @@ digraph process { "Implementer implements, tests, commits, self-reviews" [shape=box]; "Generate review package, dispatch task reviewer (./task-reviewer-prompt.md)" [shape=box]; "Spec ✅ and quality approved?" [shape=diamond]; - "Finding conflicts with plan text?" [shape=diamond]; - "Rule on the conflict, ledger the ruling" [shape=box]; - "Fix round R of 5: R≤3 resume implementer; R≥4 fresh implementer, more capable model" [shape=box]; - "Dispatch scoped re-review (./re-review-prompt.md)" [shape=box]; - "All findings addressed?" [shape=diamond]; - "R = 5?" [shape=diamond]; - "Adjudicate each open finding" [shape=box]; - "Any load-bearing finding?" [shape=diamond]; - "Rule and continue; stop only if every path forward is a guess" [shape=box]; - "Park findings in ledger with rulings" [shape=box]; + "Fix loop: read references/fix-loop.md before every round" [shape=box]; "Append completion to ledger, mark todo complete" [shape=box]; } "Setup: worktree, ledger check, read plan, pre-flight review" [shape=box]; "More tasks remain?" [shape=diamond]; - "Dispatch final code reviewer (../requesting-code-review/code-reviewer.md)" [shape=box]; - "Final findings? ONE fix dispatch, one scoped re-review, adjudicate residuals" [shape=box]; + "Final review and finish: read references/final-review.md" [shape=box]; "Final review clean: delete this plan's workspace" [shape=box]; "Use superpowers:finishing-a-development-branch" [shape=box style=filled fillcolor=lightgreen]; @@ -98,25 +88,12 @@ digraph process { "Implementer implements, tests, commits, self-reviews" -> "Generate review package, dispatch task reviewer (./task-reviewer-prompt.md)"; "Generate review package, dispatch task reviewer (./task-reviewer-prompt.md)" -> "Spec ✅ and quality approved?"; "Spec ✅ and quality approved?" -> "Append completion to ledger, mark todo complete" [label="yes"]; - "Spec ✅ and quality approved?" -> "Finding conflicts with plan text?" [label="no"]; - "Finding conflicts with plan text?" -> "Rule on the conflict, ledger the ruling" [label="yes"]; - "Rule on the conflict, ledger the ruling" -> "Fix round R of 5: R≤3 resume implementer; R≥4 fresh implementer, more capable model"; - "Finding conflicts with plan text?" -> "Fix round R of 5: R≤3 resume implementer; R≥4 fresh implementer, more capable model" [label="no"]; - "Fix round R of 5: R≤3 resume implementer; R≥4 fresh implementer, more capable model" -> "Dispatch scoped re-review (./re-review-prompt.md)"; - "Dispatch scoped re-review (./re-review-prompt.md)" -> "All findings addressed?"; - "All findings addressed?" -> "Append completion to ledger, mark todo complete" [label="yes"]; - "All findings addressed?" -> "R = 5?" [label="no"]; - "R = 5?" -> "Fix round R of 5: R≤3 resume implementer; R≥4 fresh implementer, more capable model" [label="no - next round"]; - "R = 5?" -> "Adjudicate each open finding" [label="yes - breaker trips"]; - "Adjudicate each open finding" -> "Any load-bearing finding?"; - "Any load-bearing finding?" -> "Rule and continue; stop only if every path forward is a guess" [label="yes"]; - "Any load-bearing finding?" -> "Park findings in ledger with rulings" [label="no"]; - "Park findings in ledger with rulings" -> "Append completion to ledger, mark todo complete"; + "Spec ✅ and quality approved?" -> "Fix loop: read references/fix-loop.md before every round" [label="no"]; + "Fix loop: read references/fix-loop.md before every round" -> "Append completion to ledger, mark todo complete" [label="addressed, or parked at the cap"]; "Append completion to ledger, mark todo complete" -> "More tasks remain?"; "More tasks remain?" -> "Dispatch implementer subagent (./implementer-prompt.md)" [label="yes"]; - "More tasks remain?" -> "Dispatch final code reviewer (../requesting-code-review/code-reviewer.md)" [label="no"]; - "Dispatch final code reviewer (../requesting-code-review/code-reviewer.md)" -> "Final findings? ONE fix dispatch, one scoped re-review, adjudicate residuals"; - "Final findings? ONE fix dispatch, one scoped re-review, adjudicate residuals" -> "Final review clean: delete this plan's workspace"; + "More tasks remain?" -> "Final review and finish: read references/final-review.md" [label="no"]; + "Final review and finish: read references/final-review.md" -> "Final review clean: delete this plan's workspace"; "Final review clean: delete this plan's workspace" -> "Use superpowers:finishing-a-development-branch"; } ``` @@ -134,17 +111,14 @@ sequences — the single most expensive failure observed. Track progress in a ledger file, not only in todos. - Each plan owns a workspace: at skill start, run this skill's - `bash scripts/sdd-workspace PLAN_FILE` — it prints the plan's git-ignored + `bash scripts/sdd-workspace PLAN_FILE` by its absolute path, from the + working tree you're executing in — it prints the plan's git-ignored directory (under `/.superpowers/sdd/`), home to every artifact for THIS plan: ledger, briefs, reports, review packages. Another plan's directory is never yours to read or write. -- Check for this plan's ledger at `/progress.md`. If its first - line names your plan file, tasks with a `Task : complete` line are DONE - — do not re-dispatch them; resume at the first task without one. A task - whose last line is a fix round is mid-loop: resume the loop at the next - round. A ledger whose first line names a different plan file — or a stray - ledger at the old flat path `.superpowers/sdd/progress.md` — is another - plan's progress: leave it in place and start your own, fresh. +- Check for this plan's ledger at `/progress.md`. If one exists, + read [references/resuming.md](references/resuming.md) before anything + else: it says whose ledger it is and where to resume. - Create the ledger with its identity as the first line: `# SDD ledger — plan: `. - The ledger is your recovery map: the commits it names exist in git even @@ -159,27 +133,9 @@ authority the plan argues from, and conflicts inside the plan resolve against it. A plan with no reachable spec gets a ledger note saying so — rulings made without one are provisional. -Before dispatching Task 1, scan the plan once for conflicts, writing down -what you checked as you check it: - -- tasks that contradict each other or the plan's Global Constraints -- anything the plan explicitly mandates that the review rubric treats as a - defect (a test that asserts nothing, verbatim duplication of a logic block) - -The scan's output is a table, not a verdict. One row for every pair of tasks -that share a file or an interface: the two tasks, what one produces against -what the other consumes, and what you found. One row for every task: whether -its own text agrees with itself — the tests it specifies against the code it -specifies, the files it creates against the files it later touches. "The scan -is clean" without those rows is not a scan you ran. - -Write the table to the ledger. Rule on everything you find before execution -begins — each finding against the plan text that mandates it — and record -each ruling in the ledger. If the scan is clean, proceed without comment. -Rule on each conflict it surfaces — the spec is the binding authority, the -plan is its argument — record the ruling beside its row, and dispatch -Task 1. The review loop remains the net for conflicts that only emerge from -implementation. +Before dispatching Task 1, read [references/preflight.md](references/preflight.md) +and run the conflict scan it describes. Its table and rulings go in the +ledger. ## Model Selection @@ -220,13 +176,9 @@ that implementer. Single-file mechanical fixes also take the cheapest tier. ## The Task Loop -**Batch small same-shape work.** When the plan lists several tasks that are -each a small, independent edit of the same kind — the same one-line fix, -constant change, or field addition repeated across files — do not dispatch -one subagent per task. Compose ONE dispatch brief listing every file and -its change, send the whole batch to a single subagent, and review its diff -as one unit. Reserve one-dispatch-per-task for work that needs its own -judgment, its own tests, or its own review surface. +**Small same-shape work:** when the plan lists several small edits of the +same kind across files, read [references/batching.md](references/batching.md) +before dispatching them. Everything you paste into a dispatch prompt — and everything a subagent prints back — stays resident in your context for the rest of the session @@ -249,7 +201,8 @@ Record BASE (`git rev-parse HEAD`) before dispatching — the review package and fix-round diffs need it. - **Task brief:** before dispatching an implementer, run this skill's - `bash scripts/task-brief PLAN_FILE N` — it extracts the task's full text to a + `bash scripts/task-brief PLAN_FILE N` by its absolute path, from the + working tree you're executing in — it extracts the task's full text to a uniquely named file and prints the path. Compose the dispatch so the brief stays the single source of requirements. Your dispatch should contain: (1) one line on where this @@ -287,19 +240,11 @@ Template: [implementer-prompt.md](implementer-prompt.md) Implementer subagents report one of four statuses. Handle each appropriately: -**DONE:** Generate the review package (`bash scripts/review-package PLAN_FILE BASE HEAD`, from this skill's directory — it prints the unique file path it wrote; BASE is the commit you recorded before dispatching the implementer — never `HEAD~1`, which silently drops all but the last commit of a multi-commit task), then dispatch the task reviewer with the printed path. - -**DONE_WITH_CONCERNS:** The implementer completed the work but flagged doubts. Read the concerns before proceeding. If the concerns are about correctness or scope, address them before review. If they're observations (e.g., "this file is getting large"), note them and proceed to review. +**DONE:** Generate the review package (`bash scripts/review-package PLAN_FILE BASE HEAD` by its absolute path, from the working tree you're executing in — it prints the unique file path it wrote; BASE is the commit you recorded before dispatching the implementer — never `HEAD~1`, which silently drops all but the last commit of a multi-commit task), then dispatch the task reviewer with the printed path. -**NEEDS_CONTEXT:** The implementer needs information that wasn't provided. Provide the missing context and re-dispatch. - -**BLOCKED:** The implementer cannot complete the task. Assess the blocker: -1. If it's a context problem, provide more context and re-dispatch with the same model -2. If the task requires more reasoning, re-dispatch with a more capable model -3. If the task is too large, break it into smaller pieces -4. If the plan itself is wrong, rule on the correction, ledger it, and re-dispatch with the ruling carried in the dispatch - -**Never** ignore an escalation or force the same model to retry without changes. If the implementer said it's stuck, something needs to change. +**Any other status** (DONE_WITH_CONCERNS, NEEDS_CONTEXT, BLOCKED): read +[references/handling-reports.md](references/handling-reports.md) and follow +it, every time you get one. If the implementer asks questions — before starting or mid-task — answer clearly and completely, provide additional context if needed, and don't @@ -314,7 +259,8 @@ required. Implementer self-review never replaces the task review; both are needed. - Hand the reviewer its diff as a file: run this skill's - `bash scripts/review-package PLAN_FILE BASE HEAD` and pass the reviewer the file path + `bash scripts/review-package PLAN_FILE BASE HEAD` by its absolute path, from + the working tree you're executing in, and pass the reviewer the file path it prints (or, without bash: `git log --oneline`, `git diff --stat`, and `git diff -U10` for the range, redirected to one uniquely named file). The output never enters your own context, and the reviewer sees @@ -342,12 +288,9 @@ needed. loop. If the prompt you are writing contains "do not flag," "don't treat X as a defect," "at most Minor," or "the plan chose" — stop: you are pre-judging, usually to spare yourself a review loop. -The task reviewer may report "⚠️ Cannot verify from diff" items — requirements -that live in unchanged code or span tasks. These do not block the rest of the -review, but you must resolve each one yourself before marking the task -complete: you hold the plan and cross-task context the reviewer -lacks. If you confirm an item is a real gap, treat it as a failed spec -review — it enters the fix loop with the other findings. +If the reviewer reports "⚠️ Cannot verify from diff" items, read +[references/cannot-verify.md](references/cannot-verify.md) before you mark +the task complete. Template: [task-reviewer-prompt.md](task-reviewer-prompt.md) @@ -356,77 +299,10 @@ Template: [task-reviewer-prompt.md](task-reviewer-prompt.md) The loop triggers when the review reports spec ❌, any Critical or Important finding, or a ⚠️ item you confirmed as a real gap. -Before the loop starts, two routes leave it immediately: - -- Record Minor findings in the progress ledger as you go - (`Task : minor (deferred): `), and point the final - whole-branch review at that list so it can triage which must be fixed - before merge. A roll-up nobody reads is a silent discard. Minor findings - never enter the loop. -- A finding labeled plan-mandated — or any finding that conflicts with - what the plan's text requires — is yours to rule on: weigh the finding - against the plan text, decide with the spec as the binding authority, and - ledger the ruling before you act on it. Do not dismiss the finding because - the plan mandates it, and do not dispatch a fix that contradicts the plan - without a recorded ruling. -Everything else enters the loop. A fix round is one fix dispatch plus one -scoped re-review. Five rounds maximum per task: - -**Rounds 1-3 — resume the original implementer.** Send it the open findings -verbatim. Its context is intact: it knows the task, the code, and its own -choices. If your harness cannot send another message to a live subagent, -dispatch a fresh implementer carrying the brief path, the report-file path, -and the findings — the report file is the persistent memory either way. - -**Rounds 4-5 — dispatch a fresh implementer on a more capable model** (per -Model Selection), with the brief path, the report-file path, the open -findings, and this framing: "A prior implementer attempted this task -[N] times; you own it now. Read the report file for what was tried." A loop -that survives three resumes usually means the implementer cannot see its -own problem — fresh eyes and a capability bump in one move. - -**Every round, either way:** the implementer fixes, re-runs the tests -covering the amended code, appends its fix report to the same report file, -and returns the short contract. Before re-dispatching the reviewer, confirm -the fix report contains the covering tests, the command run, and the -output; dispatch the re-review once all three are present. Name the -covering test files in the fix message — a one-line fix does not need the -whole suite. - -**The re-review is scoped.** Run `bash scripts/review-package PLAN_FILE FIX_BASE HEAD` -where FIX_BASE is the head the previous review saw, and dispatch -[re-review-prompt.md](re-review-prompt.md) with the findings list, the -brief, the report file, and the printed diff path. The re-reviewer verdicts -each finding ADDRESSED or NOT ADDRESSED and flags new breakage in the fix -diff only. New Critical/Important breakage in the fix diff joins the open -findings list. Out-of-scope observations go to the ledger as deferred -minors — they never extend the loop. - -**After each round,** append to the ledger: -`Task : fix round /5 ( addressed, open — ; commits ..)` - -Never fix findings yourself in the controller session — your context stays -clean for coordination, and controller fixes skip review. - -**The breaker.** When round 5's re-review still leaves findings open, stop -dispatching. Adjudicate each open finding yourself — you hold the plan and -the cross-task context the reviewer lacks: - -- **The reviewer is wrong, or the point is contestable:** park it — - `Task : parked — — Ruling: `. The final - review sees both sides. -- **Real, but nothing downstream builds on it:** park it the same way, with - a ruling that says it's real and deferred. -- **Real and load-bearing** — a later task builds on it, or it reveals a - plan defect: rule on the smallest change that unblocks the dependent work, - ledger it as `Task : Ruling: — `, - and carry it into the next task's dispatch. Parking a structural failure - silently lets every dependent task build on it. Stop only when the defect - leaves every path forward a guess. - -Adjudicate only at the cap. Adjudicating earlier to end a loop is -pre-judging with a different name. Every adjudication is a ledger entry — -a silent discard is forbidden. +When it triggers, read [references/fix-loop.md](references/fix-loop.md) and +follow it. Read it again before every round, even if you read it earlier in +this session: it holds the round cap, the breaker, and the ledger line each +round writes, and your memory of it does not survive compaction. ### 5. Complete the task @@ -444,47 +320,9 @@ parked-with-ruling at the cap. ## Final Review -The final whole-branch review gets a package too: run -`bash scripts/review-package PLAN_FILE MERGE_BASE HEAD` (MERGE_BASE = the commit the -branch started from, e.g. `git merge-base main HEAD`) and include the -printed path in the final review dispatch, so the final reviewer reads -one file instead of re-deriving the branch diff with git commands. Dispatch -on the most capable available model (see Model Selection), using -superpowers:requesting-code-review's -[code-reviewer.md](../requesting-code-review/code-reviewer.md). Point it at -the ledger's deferred-minor and parked lines so it can triage which must be -fixed before merge. - -If the final whole-branch review returns findings, dispatch ONE fix subagent -with the complete findings list — not one fixer per finding. -Per-finding fixers each rebuild context and re-run suites; a real -session's final-review fix wave cost more than all its tasks combined. -Then run exactly one scoped re-review of the fix wave -(`bash scripts/review-package PLAN_FILE FIX_BASE HEAD` over the fix range, -[re-review-prompt.md](re-review-prompt.md)). -Adjudicate any residual findings as in the task loop's breaker: park with -rulings, or rule on the load-bearing ones and ledger what you decided. Only -the four classes above stop you here. There is no second fix wave — -residual load-bearing findings surface to your human partner when -finishing-a-development-branch presents the options. - -## Finish - -Before you delete anything, collect every ledger line containing `Ruling:` — -preflight rulings, parked findings, breaker adjudications, all of them — into -your final message under "Rulings I made", in the order you made them, each -with what it costs if wrong. The list is exhaustive: if the ledger holds a -ruling, the list holds it. That list is the only place the decisions you -took on your human partner's behalf reach them — they read it and rework -whatever you got wrong. A ruling that dies with the workspace was a decision -made in secret. - -When the final whole-branch review is clean and its fixes are merged, -delete this plan's workspace (`rm -rf `) — the git history is -the record now. Sibling directories belong to other plans; leave them -alone. - -Use superpowers:finishing-a-development-branch. +When every task is complete, read +[references/final-review.md](references/final-review.md) and follow it. It +covers the whole-branch review, its single fix wave, and how you finish. ## Common Rationalizations @@ -499,70 +337,3 @@ Use superpowers:finishing-a-development-branch. | "Reviews slow the loop down" | The loop without reviews is just unverified churn. Reviews are the loop's brakes and steering. | | "Ledger bookkeeping is overhead" | The ledger is what survives compaction. Controllers without one have re-dispatched entire completed task sequences. | | "The implementer spawned its own reviewer — free extra assurance" | It's a duplicate seat reviewing the same diff; the task review is the gate. A worker-spawned reviewer is a defect to flag, not rigor. | - -## Example Workflow - -``` -You: I'm using Subagent-Driven Development to execute this plan. - -[Setup: worktree verified] -[Read plan file once: docs/superpowers/plans/feature-plan.md] -[Resolve workspace: bash scripts/sdd-workspace docs/superpowers/plans/feature-plan.md — no ledger inside, fresh start] -[Create todos for all tasks] - -Task 1: Hook installation script - -[Run task-brief for Task 1; dispatch implementer with brief + report paths + context] - -Implementer: "Before I begin - should the hook be installed at user or system level?" - -You: "User level (~/.config/superpowers/hooks/)" - -Implementer: [Later] - - Implemented install-hook command - - Added tests, 5/5 passing - - Self-review: Found I missed --force flag, added it - - Committed - -[Run review-package PLAN_FILE BASE HEAD; dispatch task reviewer with the printed path] -Task reviewer: Spec ✅ - all requirements met, nothing extra. - Strengths: Good test coverage, clean. Issues: None. Task quality: Approved. - -[Ledger: Task 1: complete (commits a1b2c3d..d4e5f6a, review clean)] - -Task 2: Recovery modes - -[Run task-brief for Task 2; dispatch implementer with brief + report paths + context] - -Implementer: [No questions] - - Added verify/repair modes - - 8/8 tests passing - - Committed - -[Run review-package PLAN_FILE BASE HEAD; dispatch task reviewer with the printed path] -Task reviewer: Spec ❌: - - Missing: Progress reporting (spec says "report every 100 items") - Issues (Important): Magic number (100) - -[Fix round 1: resume the implementer with both findings] -Implementer: Added progress reporting, extracted PROGRESS_INTERVAL constant. - Re-ran test/recovery.test.js — 10/10 passing. Fix report appended. - -[Run review-package PLAN_FILE FIX_BASE HEAD; dispatch scoped re-review] -Re-reviewer: Missing progress reporting — ADDRESSED (src/recovery.js:41). - Magic number — ADDRESSED (src/recovery.js:7). New breakage: none. - Verdict: all findings addressed. - -[Ledger: Task 2: fix round 1/5 (2 addressed, 0 open; commits d4e5f6a..b7c8d9e)] -[Ledger: Task 2: complete (commits d4e5f6a..b7c8d9e, review clean)] - -... - -[After all tasks] -[Run review-package PLAN_FILE MERGE_BASE HEAD; dispatch final code-reviewer, most capable model] -Final reviewer: All requirements met. Deferred minors triaged: none block merge. - -[Delete this plan's workspace — the record now lives in git] - -Done! Using superpowers:finishing-a-development-branch. -``` diff --git a/external/ai-agents/subagent-driven-development/re-review-prompt.md b/external/ai-agents/subagent-driven-development/re-review-prompt.md index d49c182..eb520df 100644 --- a/external/ai-agents/subagent-driven-development/re-review-prompt.md +++ b/external/ai-agents/subagent-driven-development/re-review-prompt.md @@ -110,6 +110,7 @@ Subagent (general-purpose): - `[FIX_BASE_SHA]` — the head the previous review saw - `[HEAD_SHA]` — current commit - `[DIFF_FILE]` — the path `bash scripts/review-package PLAN_FILE FIX_BASE HEAD` printed + (run by its absolute path, from the working tree you're executing in) **Re-reviewer returns:** per-finding verdicts (ADDRESSED / NOT ADDRESSED), new breakage in the fix diff, out-of-scope observations, and a round verdict. diff --git a/external/ai-agents/subagent-driven-development/references/batching.md b/external/ai-agents/subagent-driven-development/references/batching.md new file mode 100644 index 0000000..6224e4b --- /dev/null +++ b/external/ai-agents/subagent-driven-development/references/batching.md @@ -0,0 +1,11 @@ +# Batching Small Same-Shape Work + +Part of superpowers:subagent-driven-development. + +**Batch small same-shape work.** When the plan lists several tasks that are +each a small, independent edit of the same kind — the same one-line fix, +constant change, or field addition repeated across files — do not dispatch +one subagent per task. Compose ONE dispatch brief listing every file and +its change, send the whole batch to a single subagent, and review its diff +as one unit. Reserve one-dispatch-per-task for work that needs its own +judgment, its own tests, or its own review surface. diff --git a/external/ai-agents/subagent-driven-development/references/cannot-verify.md b/external/ai-agents/subagent-driven-development/references/cannot-verify.md new file mode 100644 index 0000000..e952f2d --- /dev/null +++ b/external/ai-agents/subagent-driven-development/references/cannot-verify.md @@ -0,0 +1,10 @@ +# "Cannot Verify From Diff" Items + +Part of superpowers:subagent-driven-development. + +The task reviewer may report "⚠️ Cannot verify from diff" items — requirements +that live in unchanged code or span tasks. These do not block the rest of the +review, but you must resolve each one yourself before marking the task +complete: you hold the plan and cross-task context the reviewer +lacks. If you confirm an item is a real gap, treat it as a failed spec +review — it enters the fix loop with the other findings. diff --git a/external/ai-agents/subagent-driven-development/references/final-review.md b/external/ai-agents/subagent-driven-development/references/final-review.md new file mode 100644 index 0000000..cf03546 --- /dev/null +++ b/external/ai-agents/subagent-driven-development/references/final-review.md @@ -0,0 +1,49 @@ +# Final Review and Finish + +Part of superpowers:subagent-driven-development. Every task in the plan is +complete. + +The final whole-branch review gets a package too: run +`bash scripts/review-package PLAN_FILE MERGE_BASE HEAD` by its absolute path, +from the working tree you're executing in (MERGE_BASE = the commit the +branch started from, e.g. `git merge-base main HEAD`) and include the +printed path in the final review dispatch, so the final reviewer reads +one file instead of re-deriving the branch diff with git commands. Dispatch +on the most capable available model (see SKILL.md's Model Selection), using +superpowers:requesting-code-review's +[code-reviewer.md](../../requesting-code-review/code-reviewer.md). Point it at +the ledger's deferred-minor and parked lines so it can triage which must be +fixed before merge. + +If the final whole-branch review returns findings, dispatch ONE fix subagent +with the complete findings list — not one fixer per finding. +Per-finding fixers each rebuild context and re-run suites; a real +session's final-review fix wave cost more than all its tasks combined. +Then run exactly one scoped re-review of the fix wave +(`bash scripts/review-package PLAN_FILE FIX_BASE HEAD` by its absolute path, +from the working tree you're executing in, over the fix range, +[re-review-prompt.md](../re-review-prompt.md)). +Adjudicate any residual findings as in the fix loop's breaker +(references/fix-loop.md): park with rulings, or rule on the load-bearing +ones and ledger what you decided. Only the four things that stop a running +plan (top of SKILL.md) stop you here. There is no second fix wave — +residual load-bearing findings surface to your human partner when +finishing-a-development-branch presents the options. + +### Finish + +Before you delete anything, collect every ledger line containing `Ruling:` — +preflight rulings, parked findings, breaker adjudications, all of them — into +your final message under "Rulings I made", in the order you made them, each +with what it costs if wrong. The list is exhaustive: if the ledger holds a +ruling, the list holds it. That list is the only place the decisions you +took on your human partner's behalf reach them — they read it and rework +whatever you got wrong. A ruling that dies with the workspace was a decision +made in secret. + +When the final whole-branch review is clean and its fixes are merged, +delete this plan's workspace (`rm -rf `) — the git history is +the record now. Sibling directories belong to other plans; leave them +alone. + +Use superpowers:finishing-a-development-branch. diff --git a/external/ai-agents/subagent-driven-development/references/fix-loop.md b/external/ai-agents/subagent-driven-development/references/fix-loop.md new file mode 100644 index 0000000..34e4a48 --- /dev/null +++ b/external/ai-agents/subagent-driven-development/references/fix-loop.md @@ -0,0 +1,77 @@ +# The Fix Loop + +Part of superpowers:subagent-driven-development. The task review reported +spec ❌, a Critical or Important finding, or a ⚠️ item you confirmed as a real +gap. + +Before the loop starts, two routes leave it immediately: + +- Record Minor findings in the progress ledger as you go + (`Task : minor (deferred): `), and point the final + whole-branch review at that list so it can triage which must be fixed + before merge. A roll-up nobody reads is a silent discard. Minor findings + never enter the loop. +- A finding labeled plan-mandated — or any finding that conflicts with + what the plan's text requires — is yours to rule on: weigh the finding + against the plan text, decide with the spec as the binding authority, and + ledger the ruling before you act on it. Do not dismiss the finding because + the plan mandates it, and do not dispatch a fix that contradicts the plan + without a recorded ruling. +Everything else enters the loop. A fix round is one fix dispatch plus one +scoped re-review. Five rounds maximum per task: + +**Rounds 1-3 — resume the original implementer.** Send it the open findings +verbatim. Its context is intact: it knows the task, the code, and its own +choices. If your harness cannot send another message to a live subagent, +dispatch a fresh implementer carrying the brief path, the report-file path, +and the findings — the report file is the persistent memory either way. + +**Rounds 4-5 — dispatch a fresh implementer on a more capable model** (per +SKILL.md's Model Selection), with the brief path, the report-file path, the open +findings, and this framing: "A prior implementer attempted this task +[N] times; you own it now. Read the report file for what was tried." A loop +that survives three resumes usually means the implementer cannot see its +own problem — fresh eyes and a capability bump in one move. + +**Every round, either way:** the implementer fixes, re-runs the tests +covering the amended code, appends its fix report to the same report file, +and returns the short contract. Before re-dispatching the reviewer, confirm +the fix report contains the covering tests, the command run, and the +output; dispatch the re-review once all three are present. Name the +covering test files in the fix message — a one-line fix does not need the +whole suite. + +**The re-review is scoped.** Run `bash scripts/review-package PLAN_FILE FIX_BASE HEAD` +by its absolute path, from the working tree you're executing in, where FIX_BASE is the head the previous review saw, and dispatch +[re-review-prompt.md](../re-review-prompt.md) with the findings list, the +brief, the report file, and the printed diff path. The re-reviewer verdicts +each finding ADDRESSED or NOT ADDRESSED and flags new breakage in the fix +diff only. New Critical/Important breakage in the fix diff joins the open +findings list. Out-of-scope observations go to the ledger as deferred +minors — they never extend the loop. + +**After each round,** append to the ledger: +`Task : fix round /5 ( addressed, open — ; commits ..)` + +Never fix findings yourself in the controller session — your context stays +clean for coordination, and controller fixes skip review. + +**The breaker.** When round 5's re-review still leaves findings open, stop +dispatching. Adjudicate each open finding yourself — you hold the plan and +the cross-task context the reviewer lacks: + +- **The reviewer is wrong, or the point is contestable:** park it — + `Task : parked — — Ruling: `. The final + review sees both sides. +- **Real, but nothing downstream builds on it:** park it the same way, with + a ruling that says it's real and deferred. +- **Real and load-bearing** — a later task builds on it, or it reveals a + plan defect: rule on the smallest change that unblocks the dependent work, + ledger it as `Task : Ruling: — `, + and carry it into the next task's dispatch. Parking a structural failure + silently lets every dependent task build on it. Stop only when the defect + leaves every path forward a guess. + +Adjudicate only at the cap. Adjudicating earlier to end a loop is +pre-judging with a different name. Every adjudication is a ledger entry — +a silent discard is forbidden. diff --git a/external/ai-agents/subagent-driven-development/references/handling-reports.md b/external/ai-agents/subagent-driven-development/references/handling-reports.md new file mode 100644 index 0000000..cf91cdc --- /dev/null +++ b/external/ai-agents/subagent-driven-development/references/handling-reports.md @@ -0,0 +1,15 @@ +# Handling a Report That Isn't DONE + +Part of superpowers:subagent-driven-development. + +**DONE_WITH_CONCERNS:** The implementer completed the work but flagged doubts. Read the concerns before proceeding. If the concerns are about correctness or scope, address them before review. If they're observations (e.g., "this file is getting large"), note them and proceed to review. + +**NEEDS_CONTEXT:** The implementer needs information that wasn't provided. Provide the missing context and re-dispatch. + +**BLOCKED:** The implementer cannot complete the task. Assess the blocker: +1. If it's a context problem, provide more context and re-dispatch with the same model +2. If the task requires more reasoning, re-dispatch with a more capable model +3. If the task is too large, break it into smaller pieces +4. If the plan itself is wrong, rule on the correction, ledger it, and re-dispatch with the ruling carried in the dispatch + +**Never** ignore an escalation or force the same model to retry without changes. If the implementer said it's stuck, something needs to change. diff --git a/external/ai-agents/subagent-driven-development/references/preflight.md b/external/ai-agents/subagent-driven-development/references/preflight.md new file mode 100644 index 0000000..1701648 --- /dev/null +++ b/external/ai-agents/subagent-driven-development/references/preflight.md @@ -0,0 +1,26 @@ +# The Pre-flight Scan + +Part of superpowers:subagent-driven-development. Run it once, before +dispatching Task 1. + +Before dispatching Task 1, scan the plan once for conflicts, writing down +what you checked as you check it: + +- tasks that contradict each other or the plan's Global Constraints +- anything the plan explicitly mandates that the review rubric treats as a + defect (a test that asserts nothing, verbatim duplication of a logic block) + +The scan's output is a table, not a verdict. One row for every pair of tasks +that share a file or an interface: the two tasks, what one produces against +what the other consumes, and what you found. One row for every task: whether +its own text agrees with itself — the tests it specifies against the code it +specifies, the files it creates against the files it later touches. "The scan +is clean" without those rows is not a scan you ran. + +Write the table to the ledger. Rule on everything you find before execution +begins — each finding against the plan text that mandates it — and record +each ruling in the ledger. If the scan is clean, proceed without comment. +Rule on each conflict it surfaces — the spec is the binding authority, the +plan is its argument — record the ruling beside its row, and dispatch +Task 1. The review loop remains the net for conflicts that only emerge from +implementation. diff --git a/external/ai-agents/subagent-driven-development/references/resuming.md b/external/ai-agents/subagent-driven-development/references/resuming.md new file mode 100644 index 0000000..409b826 --- /dev/null +++ b/external/ai-agents/subagent-driven-development/references/resuming.md @@ -0,0 +1,12 @@ +# Resuming From an Existing Ledger + +Part of superpowers:subagent-driven-development. This plan's workspace +already has a ledger at `/progress.md`. + +If the ledger's first line names your plan file, tasks with a `Task : +complete` line are DONE — do not re-dispatch them; resume at the first +task without one. A task whose last line is a fix round is mid-loop: +resume the loop at the next round. A ledger whose first line names a +different plan file — or a stray ledger at the old flat path +`.superpowers/sdd/progress.md` — is another plan's progress: leave it in +place and start your own, fresh. diff --git a/external/ai-agents/subagent-driven-development/scripts/review-package b/external/ai-agents/subagent-driven-development/scripts/review-package index fa7625f..04c2b91 100755 --- a/external/ai-agents/subagent-driven-development/scripts/review-package +++ b/external/ai-agents/subagent-driven-development/scripts/review-package @@ -4,13 +4,22 @@ # call. Using the recorded per-task BASE (not HEAD~1) keeps multi-commit # tasks intact. # -# Usage: review-package PLAN_FILE BASE HEAD [OUTFILE] +# Usage: review-package [-C DIR] PLAN_FILE BASE HEAD [OUTFILE] # Default OUTFILE: /.superpowers/sdd//review-...diff # (named per range, so a re-review after fixes gets a distinct fresh file). +# The repository is the one sdd-repo names; -C DIR runs as if started in DIR, +# as in sdd-workspace. set -euo pipefail +here=$(cd "$(dirname "$0")" && pwd) +if [ "${1:-}" = "-C" ]; then + [ -d "${2:-}" ] || { echo "bad -C directory: ${2:-}" >&2; exit 2; } + cd "$2" + shift 2 +fi + if [ $# -lt 3 ] || [ $# -gt 4 ]; then - echo "usage: review-package PLAN_FILE BASE HEAD [OUTFILE]" >&2 + echo "usage: review-package [-C DIR] PLAN_FILE BASE HEAD [OUTFILE]" >&2 exit 2 fi @@ -19,35 +28,37 @@ base=$2 head=$3 [ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; } -git rev-parse --verify --quiet "$base" >/dev/null || { echo "bad BASE: $base" >&2; exit 2; } -git rev-parse --verify --quiet "$head" >/dev/null || { echo "bad HEAD: $head" >&2; exit 2; } +# Invoke via bash rather than direct exec: some extractors (Python zipfile) +# strip Unix exec bits when unpacking marketplace packages (#2040). +repo=$("${BASH:-bash}" "$here/sdd-repo" --with-commit) + +git -C "$repo" rev-parse --verify --quiet "$base" >/dev/null || { echo "bad BASE: $base" >&2; exit 2; } +git -C "$repo" rev-parse --verify --quiet "$head" >/dev/null || { echo "bad HEAD: $head" >&2; exit 2; } # Range guards (exit 3): a wrong-branch HEAD yields a range that is empty or # not rooted at BASE; either would silently produce a bogus review package. -git merge-base --is-ancestor "$base" "$head" || { echo "HEAD is not a descendant of BASE: ${base}..${head}" >&2; exit 3; } -[ "$(git rev-list --count "${base}..${head}")" -gt 0 ] || { echo "empty commit range: ${base}..${head}" >&2; exit 3; } +git -C "$repo" merge-base --is-ancestor "$base" "$head" || { echo "HEAD is not a descendant of BASE: ${base}..${head}" >&2; exit 3; } +[ "$(git -C "$repo" rev-list --count "${base}..${head}")" -gt 0 ] || { echo "empty commit range: ${base}..${head}" >&2; exit 3; } if [ $# -eq 4 ]; then out=$4 else - # Invoke via bash rather than direct exec: some extractors (Python zipfile) - # strip Unix exec bits when unpacking marketplace packages (#2040). - dir=$("${BASH:-bash}" "$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan") - out="$dir/review-$(git rev-parse --short "$base")..$(git rev-parse --short "$head").diff" + dir=$("${BASH:-bash}" "$here/sdd-workspace" "$plan") + out="$dir/review-$(git -C "$repo" rev-parse --short "$base")..$(git -C "$repo" rev-parse --short "$head").diff" fi { echo "# Review package: ${base}..${head}" echo echo "## Commits" - git log --oneline "${base}..${head}" + git -C "$repo" log --oneline "${base}..${head}" echo echo "## Files changed" - git diff --stat "${base}..${head}" + git -C "$repo" diff --stat "${base}..${head}" echo echo "## Diff" - git diff -U10 "${base}..${head}" + git -C "$repo" diff -U10 "${base}..${head}" } > "$out" -commits=$(git rev-list --count "${base}..${head}") +commits=$(git -C "$repo" rev-list --count "${base}..${head}") echo "wrote ${out}: ${commits} commit(s), $(wc -c < "$out" | tr -d ' ') bytes" diff --git a/external/ai-agents/subagent-driven-development/scripts/sdd-repo b/external/ai-agents/subagent-driven-development/scripts/sdd-repo new file mode 100755 index 0000000..b1756b9 --- /dev/null +++ b/external/ai-agents/subagent-driven-development/scripts/sdd-repo @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Print the top-level directory of the git working tree the SDD scripts use: +# the current directory's, exactly as git itself picks one. A caller that +# means a different working tree says so with the scripts' -C DIR, which +# they apply (cd) before calling this. +# +# The plan file's location never picks the repository. An executor working +# in a linked worktree commonly holds an absolute path to the plan in the +# main checkout (writing-plans leaves the plan uncommitted there), and its +# workspace and commits belong to the worktree it is working in. And a +# caller standing outside any repository, such as the skill's own directory +# in an installed plugin, is in the wrong place: guessing a repository from +# the plan would silently act on whichever checkout the path points into. +# +# Outside any working tree, stop. --with-commit also stops on a repository +# with no commit yet, for callers that need HEAD as a BASE or a review +# range. The message covers both ways to get here: running from the wrong +# directory (where 'git init' would be the wrong fix) and a new project +# that has no repository or commit yet. +# +# Single source of truth for which repository the SDD and executing-plans +# scripts work in, so no two of them can disagree. +# +# Usage: sdd-repo [--with-commit] +set -euo pipefail + +with_commit=false +if [ "${1:-}" = "--with-commit" ]; then + with_commit=true + shift +fi + +if [ $# -ne 0 ]; then + echo "usage: sdd-repo [--with-commit]" >&2 + exit 2 +fi + +if root=$(git rev-parse --show-toplevel 2>/dev/null); then + if [ "$with_commit" = false ] || git -C "$root" rev-parse --verify --quiet HEAD >/dev/null; then + printf '%s\n' "$root" + exit 0 + fi + said=$(git -C "$root" rev-parse --verify HEAD 2>&1 >/dev/null) || true +else + said=$(git rev-parse --show-toplevel 2>&1 >/dev/null) || true +fi + +# git's own first line always follows the shared message: where git refuses +# a working tree the caller is inside (dubious ownership, git missing from +# PATH, the cwd inside .git), the shared message alone would be wrong, and +# git's line names the real fix. +echo "SDD scripts must run from inside the git working tree you're executing in, with at least one commit (current directory: $PWD). If this is a new project, run 'git init' and make a first commit, then rerun." >&2 +echo "git said: ${said%%$'\n'*}" >&2 +exit 2 diff --git a/external/ai-agents/subagent-driven-development/scripts/sdd-workspace b/external/ai-agents/subagent-driven-development/scripts/sdd-workspace index ff6b983..ca3ac3b 100755 --- a/external/ai-agents/subagent-driven-development/scripts/sdd-workspace +++ b/external/ai-agents/subagent-driven-development/scripts/sdd-workspace @@ -27,11 +27,23 @@ # Single source of truth for the workspace location, so task-brief and # review-package cannot drift to different directories. # -# Usage: sdd-workspace PLAN_FILE +# The repository is the current directory's (see sdd-repo). -C DIR runs as +# if started in DIR, the way git -C does: a caller working on another +# checkout or worktree needs no `cd DIR ;` prefix, a compound that harnesses +# cannot allowlist because DIR differs on every run (#1799). +# +# Usage: sdd-workspace [-C DIR] PLAN_FILE set -euo pipefail +here=$(cd "$(dirname "$0")" && pwd) +if [ "${1:-}" = "-C" ]; then + [ -d "${2:-}" ] || { echo "bad -C directory: ${2:-}" >&2; exit 2; } + cd "$2" + shift 2 +fi + if [ $# -ne 1 ]; then - echo "usage: sdd-workspace PLAN_FILE" >&2 + echo "usage: sdd-workspace [-C DIR] PLAN_FILE" >&2 exit 2 fi @@ -42,13 +54,20 @@ slug=$(basename "$plan" .md) [ -n "$slug" ] && [ "$slug" != "." ] && [ "$slug" != ".." ] \ || { echo "cannot derive a workspace name from: $plan" >&2; exit 2; } -root=$(git rev-parse --show-toplevel) +# Physical path, spelled the way pwd -P spells the plan path below: on Git +# Bash, rev-parse prints C:/Users/... while pwd prints /c/Users/..., and the +# repo-relative marker check needs both in the same form. Two steps, so a +# failing sdd-repo stops the script instead of handing cd an empty path. +# Invoke via bash rather than direct exec: some extractors (Python zipfile) +# strip Unix exec bits when unpacking marketplace packages (#2040). +toplevel=$("${BASH:-bash}" "$here/sdd-repo") +root=$(CDPATH='' cd -- "$toplevel" && pwd -P) base="$root/.superpowers/sdd" # Normalize the plan path (physical directory, so relative/absolute/../ # spellings of one plan compare equal) and express it as the marker value: # repo-relative when the plan lives under the repo root, absolute otherwise. -plan_dir=$(CDPATH= cd -- "$(dirname "$plan")" && pwd -P) +plan_dir=$(CDPATH='' cd -- "$(dirname "$plan")" && pwd -P) plan_abs="$plan_dir/$(basename "$plan")" case "$plan_abs" in "$root"/*) plan_id=${plan_abs#"$root"/} ;; @@ -78,5 +97,14 @@ if ! owns "$dir"; then fi fi -printf '*\n' > "$base/.gitignore" -CDPATH= cd -- "$dir" && pwd +# Create-only: a repo may commit its own .gitignore here (e.g. to version +# progress.md ledgers); overwriting it would revert that on every run. +[ -e "$base/.gitignore" ] || printf '*\n' > "$base/.gitignore" + +# On Windows (Git Bash, MSYS2, Cygwin) print C:/Users/... rather than +# /c/Users/... or /tmp/...: the caller may be PowerShell or another native +# tool, which can't resolve MSYS paths, and bash accepts this form too. +# Only the printed path changes; the plan-path markers above keep pwd -P's +# spelling, so existing workspaces still match their plans. +CDPATH='' cd -- "$dir" +if command -v cygpath >/dev/null 2>&1; then cygpath -m "$PWD"; else pwd; fi diff --git a/external/ai-agents/subagent-driven-development/scripts/task-brief b/external/ai-agents/subagent-driven-development/scripts/task-brief index b49fc54..a490211 100755 --- a/external/ai-agents/subagent-driven-development/scripts/task-brief +++ b/external/ai-agents/subagent-driven-development/scripts/task-brief @@ -3,14 +3,21 @@ # implementer reads in one call, so the task text never has to be pasted # through the controller's context. # -# Usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE] +# Usage: task-brief [-C DIR] PLAN_FILE TASK_NUMBER [OUTFILE] # Default OUTFILE: /.superpowers/sdd//task--brief.md # (per plan and per worktree; concurrent runs of the SAME plan in the same -# working tree share it). +# working tree share it). -C DIR runs as if started in DIR, as in sdd-workspace. set -euo pipefail +here=$(cd "$(dirname "$0")" && pwd) +if [ "${1:-}" = "-C" ]; then + [ -d "${2:-}" ] || { echo "bad -C directory: ${2:-}" >&2; exit 2; } + cd "$2" + shift 2 +fi + if [ $# -lt 2 ] || [ $# -gt 3 ]; then - echo "usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]" >&2 + echo "usage: task-brief [-C DIR] PLAN_FILE TASK_NUMBER [OUTFILE]" >&2 exit 2 fi @@ -23,17 +30,75 @@ if [ $# -eq 3 ]; then else # Invoke via bash rather than direct exec: some extractors (Python zipfile) # strip Unix exec bits when unpacking marketplace packages (#2040). - dir=$("${BASH:-bash}" "$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan") + dir=$("${BASH:-bash}" "$here/sdd-workspace" "$plan") out="$dir/task-${n}-brief.md" fi -awk -v n="$n" ' - /^```/ { infence = !infence } - !infence && /^#+[ \t]+Task[ \t]+[0-9]+/ { - intask = ($0 ~ ("^#+[ \t]+Task[ \t]+" n "([^0-9]|$)")) +# A task heading is `Task` followed by an ID: letters and digits, optionally +# dot-separated, containing at least one digit (3, 6b, A1, 2.1). The digit +# keeps headings like "## Task Overview" or "#### Task notes" from becoming +# task boundaries. The ID is compared to TASK_NUMBER as a whole string, so +# Task 6 never matches Task 6b, Task 1 never matches Task 1.1, and +# TASK_NUMBER never reaches awk as a regex (#2459). A deeper Task heading +# whose ID extends the requested one with a letter or dot is a sub-task and +# stays in its brief (### Task 7 keeps #### Task 7a), as it always has. +# Depth alone is not enough: plans drift from "## Task 1" to "### Task 2". +# +# The word stays the English literal "Task", whatever language the rest of +# the plan is in. A plan with no Task headings at all usually translated it +# ("## Tarea 1"), so list the headings shaped like tasks before the +# not-found error instead of implying the plan has no task 1 (#2496). +# +# A fence that never closes hides every Task heading after it (#2436). When +# it hides one while the requested task is being captured, that task's end +# can't be found and the brief would run on through every later task: refuse +# (exit 4) rather than hand an implementer several tasks as its requirements. +# When it hides the requested heading itself, say so before the not-found +# error. A stray fence that hides no Task heading (real plans end with a lone +# ``` line) leaves the brief complete, so brief it with a warning. +# The plan path goes through ENVIRON, not -v, which would mangle the +# backslashes of a Windows path. +rc=0 +TASK_BRIEF_PLAN="$plan" awk -v n="$n" ' + /^```/ { infence = !infence; if (infence) { fence_line = NR; hidden = 0 } } + infence && /^#+[ \t]+Task[ \t]/ { hidden = 1 } + !infence && /^#+[ \t]+Task[ \t]/ { + id = $0 + sub(/^#+[ \t]+Task[ \t]+/, "", id) + if (match(id, /^[A-Za-z0-9]+(\.[A-Za-z0-9]+)*/) && substr(id, 1, RLENGTH) ~ /[0-9]/) { + tasks++ + id = substr(id, 1, RLENGTH) + match($0, /^#+/) + subtask = intask && RLENGTH > level && substr(id, 1, length(n)) == n \ + && substr(id, length(n) + 1, 1) ~ /[A-Za-z.]/ + if (!subtask) { + intask = (id == n) + level = RLENGTH + } + } + } + !infence && /^#+[ \t]+[^ \t]+[ \t]+[A-Za-z0-9.]*[0-9]/ && shaped < 5 { shaped_heading[++shaped] = $0 } + intask { print; found = 1 } + END { + where = "the code fence opened at line " fence_line " of " ENVIRON["TASK_BRIEF_PLAN"] " never closes" + if (infence && hidden && (intask || !found)) { + print "task-brief: " where ", so no Task heading after it can be seen; close that fence in the plan and rerun" | "cat >&2" + if (intask) exit 4 + } else if (infence && intask) { + print "task-brief: warning: " where "; it hides no Task heading, so this brief is complete" | "cat >&2" + } + # An open fence that hid Task headings already explains why none were + # seen; the English-word hint would contradict it. + if (!tasks && shaped && !(infence && hidden)) { + print "task-brief: no task headings found. Task headings must use the English word \"Task\" (\"### Task 1: ...\"), even when the rest of the plan is in another language. Headings shaped like tasks:" | "cat >&2" + for (i = 1; i <= shaped; i++) print " " shaped_heading[i] | "cat >&2" + } } - intask { print } -' "$plan" > "$out" +' "$plan" > "$out" || rc=$? +if [ "$rc" -ne 0 ]; then + if [ -f "$out" ]; then rm -f "$out"; fi + exit "$rc" +fi if [ ! -s "$out" ]; then echo "task ${n} not found in ${plan} (no heading matching 'Task ${n}')" >&2 diff --git a/external/ai-agents/subagent-driven-development/task-reviewer-prompt.md b/external/ai-agents/subagent-driven-development/task-reviewer-prompt.md index 5c619bc..4cf7860 100644 --- a/external/ai-agents/subagent-driven-development/task-reviewer-prompt.md +++ b/external/ai-agents/subagent-driven-development/task-reviewer-prompt.md @@ -189,7 +189,8 @@ Subagent (general-purpose): **Placeholders:** - `[MODEL]` — REQUIRED: reviewer model per SKILL.md Model Selection -- `[BRIEF_FILE]` — REQUIRED: the task brief file (`bash scripts/task-brief PLAN N` +- `[BRIEF_FILE]` — REQUIRED: the task brief file (`bash scripts/task-brief PLAN N`, + run by its absolute path, from the working tree you're executing in, prints the path; same file the implementer worked from) - `[GLOBAL_CONSTRAINTS]` — the binding requirements copied verbatim from the plan's Global Constraints section or the spec: exact values, formats, @@ -200,7 +201,8 @@ Subagent (general-purpose): - `[BASE_SHA]` — commit before this task - `[HEAD_SHA]` — current commit - `[DIFF_FILE]` — REQUIRED: the path the controller wrote the review - package to (`bash scripts/review-package PLAN_FILE BASE HEAD` prints the unique + package to (`bash scripts/review-package PLAN_FILE BASE HEAD`, run by its + absolute path, from the working tree you're executing in, prints the unique path it wrote; the package never enters the controller's context) **Reviewer returns:** Spec Compliance verdict (✅/❌/⚠️), Strengths, Issues diff --git a/external/basic/brainstorming/SKILL.md b/external/basic/brainstorming/SKILL.md index e3f1788..9eb0edc 100644 --- a/external/basic/brainstorming/SKILL.md +++ b/external/basic/brainstorming/SKILL.md @@ -1,285 +1,316 @@ --- name: brainstorming -description: "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation." +description: "You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation)." --- -# Brainstorming Ideas Into Designs - -Help turn ideas into fully formed designs and specs through natural collaborative dialogue. - -Start by classifying how much process the request needs, then work -through your path: understand the context, refine the idea, present a -design, and get your human partner's approval. - -## Establish Shared Understanding - -The outcome of brainstorming is an understanding your human partner can -recognize and correct, grounded in what they want to accomplish. - -1. **Discover intent.** Use the request and available context to identify - the intended outcome, who it is for, and what success looks like. When - that information is missing, ask one focused question about purpose or - intended use before proposing features or an approach. Knowing the app - genre does not tell you why your partner wants it. Gathering missing - requirements does not ask them to authorize the task again. -2. **Write back your understanding.** Summarize the intended outcome, - relevant constraints, and success criteria in a short note your partner - can assess. Separate what they said from assumptions. Invite correction - and incorporate their answer before treating this as the design brief. -3. **Carry intent into the design.** Preserve the agreed understanding in - the selected path's design artifact: the written spec for architectural - work, or the in-chat design/probe for bounded work and spikes. Check - proposed features and technical choices against that understanding. - -When the request already supplies the purpose and constraints, reflect -that understanding instead of asking the same questions again. Keep the -note concise; its accuracy and the opportunity to correct it matter. +# Brainstorming + +You are very good at building. You build what you believe your human +partner wants, and that belief is usually thinner than it feels. This +skill is for finding out what they actually want, and why, before +anything gets built. + +People often haven't finished working out what they want. Good questions +help them think it through. When you understand the why, you make the +hundred decisions they never mention the way they would have made them. + +## First: What Do You Actually Know? + +Before you reply, ask yourself: what do I know about what they want, and +why? + +- **Everything that matters is in the request** ("make the icon cornflower + blue"). This is a quick, clear task. Do it, and say what you did. +- **Something non-trivial is missing.** Start the conversation. + +Before you start implementing, you must, in order: + +1. Lay out the approaches in a message of their own, and have your + human partner pick one (see Lay out the approaches). +2. Write the design doc, short or full (see Size the Work). +3. Get their approval of that doc. +4. Invoke the superpowers:writing-plans skill. + +If nothing is open when you write your first message and there's one +sensible way, steps 1 to 3 happen in that message (see Your First +Message). + +Two things aren't implementing: a spike they said yes to (see Spike to +feel things out), and doing what they asked once they've opted out of +the questions. + +## Look Before You Ask + +In an existing project, or when they've given you context (a spec, a plan, +a handoff, files they pointed at, earlier conversation), dig in before your +first message. Read what they referred to and the code or material it +connects to, and keep reading until you can explain how the relevant +parts work today and what this change would touch. Never ask what the +project or their context already answers, or reopen what they've +already decided. + +## Your First Message + +Move the work forward, with one of these: + +- **They've already said what they want** (a spec, a plan, a detailed + request): aim for about 200 words for the whole message. Echo their + request back in your own words, so they can see whether you've got its + shape: what they want, who it's for, and why, as far as they've said. + Add what you found that bears on it: how the relevant parts work today + and what the change would touch. If you found something they may not + have considered that would change what they're asking for or how it + has to be built, like a constraint, a conflict, or a cheaper path, say + so plainly. How to build it comes later, once you understand what they + want (see Lay out the approaches). End with one line inviting + corrections. Then, if they haven't told you why they want it, even if + you can guess, ask about that: one open question in their own terms, + with nothing to pick from. If they have told you, and their request + and the project leave open something whose answer would change what + gets built, ask about the one that would change it most; when its + choices can be named, sketch each concretely, with what it trades off + and which you'd pick. If nothing like that is open, there's one + sensible way: end with the short design itself (the approach, a line on + why the others don't fit, and the design in a few lines) and ask them + to approve it. Their approval covers the approach and the design: write + the short design doc as approved and go on to the plan. + Specs are often incomplete, so confirm before you build on one. +- **They haven't:** one open question, in their own terms, about the + biggest thing their request leaves unsettled. Say why you're asking: + what the answer would change about what you build. For "a website for + my bakery": "If it's mostly for people deciding whether to stop by, + I'd lead with hours and what's in the case today; taking orders is a + different site. What do you want someone to do when they land on it?" + +How the message ends depends on whether their message asked for +brainstorming: + +- **They asked for it** (by name, by invoking it, or by asking for a + design discussion): end there. They want this conversation. +- **They didn't:** they may not want a conversation at all. End with a + light, one-line offer to just get going instead, like + "Or if you'd rather I just dive in, say so." If they take you up on + it, this skill is done: state any significant how-it-gets-made choice + (platform, language, medium) in one plain line, then do what they + asked through the normal workflow. + +That's the whole message. + +## The Conversation + +Ask a question only when the answer would change what gets built and +neither the request nor the project answers it. Otherwise, state your +call and your reason as a proposal they can correct, and move the work +forward. Process steps are always your calls: writing things up, where +files go, looking further, how to show something. Do them and say so. +Ask one at a time. Use plain language, pitched just a little above where +your human partner is. Match their vocabulary; never use process jargon. +You never need to quote this skill to your human partner. + +Be clear and concise. Lead with what matters. Avoid jargon. When you +report what you found, give them the most important findings and offer +the rest. + +**Get them describing.** Until you know why they want it, ask open +questions, with no example answers attached. Their own words carry +intent that your options can't. Once you know why, when a decision has +choices you can name, sketch each concretely, with what it trades off +and which you'd pick. When they say "just guess" or "what do you think?", +propose something concrete. + +**How it gets made.** Every project rests on choices about medium, tools, +materials, and resources: the platform, programming language, and +libraries for an app; the medium for a piece of art. Read from how they +talk which of those choices they want to make. Someone who raises the how +or asks about trade-offs wants the options with your pick. Someone who +talks about outcomes is handing you the how: make those calls and state +each in a line. When there's existing work, build on what it already +uses. Any choice you make goes in the design as your call, never as +something they agreed to. + +**Ground to cover.** This is territory, not a script. Never read these +out as questions: + +- what kind of thing this is, and what makes it special +- why now, and what success looks like to them +- goals, non-goals, and anti-goals (what would make this a failure even + if it technically works) +- prior art: what they've seen elsewhere, loved or hated +- current state: what exists now, what they've tried +- the details they care about + +**Make every question specific** to their project and what they've told +you. Ask Socratically, the way a sharp colleague would: build each +question on their last answer or on something you found, so it helps +them think through a consequence or a case they may have missed +("Voting closes before the meeting, and two members joined this month. +Does someone who joins mid-round get a vote?"). When the reason isn't +already plain, say what you're hoping to learn or what the answer would +change. A proposal they can correct, plus your reason for it, often +beats a question. + +**Do recon.** Once you have a bit of grounding and looking further +would help, go look: deeper into their codebase or project, their own +files and data, or the web. Say what you looked for and what you found. + +**Lay out the approaches.** Once you understand what they want, and +before you write anything up, put the ways of building it in a message +of its own. Come up with several real alternatives and drop the weak +ones, including any that work against why they want it and any the +existing work rules out. Sketch the two or three that remain concretely +enough to picture, grounded in what the project actually does, with what +each trades off. Recommend one and say why, then ask which way they want +to go; if they've handed you the how, say you'll go with your pick +instead. When there's genuinely one sensible way, say so in that +message and explain why the others don't fit; the design comes in the +next one. This is for how it gets built; for what they want, get them +describing first. + +**Show, don't tell.** Whenever seeing would help more than reading, +sketch it in text: a few lines of what they'd see. Offer the visual +companion (below) when the look itself is theirs to decide: + +- something they'll look at: a screen, a page, a printout, a sign +- a layout or arrangement: of a screen, a room, a schedule +- a flow or a sequence of steps +- options that would look different from each other +- a structure that's easier to see than read: a diagram, a timeline, a map + +Once they've accepted, use it for mockups to react to, side-by-side +options, and throwaway prototypes they can click. + +**Spike to feel things out.** Agentic work is waterfall, but very, very +fast. A quick throwaway build is often the cheapest way to learn what +they want or whether something works. Offer one when it would help. +When they just want to spike, get out of the way. Spikes get no tests +or minimal ones, aren't bulletproof, and skip the plan, implementation, +and review process. Bring what the spike taught you back into the +conversation. + +## Play It Back + +Play back as you go. As soon as you understand one part well enough to +describe it (what it's for and who it serves, say, or how one piece +behaves), describe that part back in about 200-300 words, then return to +questions about the next part. Playback and questions alternate through +the whole conversation; the last chunk covers whatever is left. + +Only what they said goes in as theirs. Anything you filled in goes in +as your proposal, with your reason. End each chunk by asking what's wrong +or missing. + +Before you send a chunk, check it: does it describe something they'll +look at, like a page, a screen, a schedule, or a printout? If so, put a +text sketch next to the description. If the look itself is theirs to +decide and they haven't seen the visual companion offer yet, send the +offer instead (its own message, below) and hold the chunk for the next +message. Once they've accepted, put a mockup next to the description. + +## Size the Work + +Once you understand what they want and they've picked an approach, say +how big the work is in plain words. They can override it. + +| Size | The design doc | +|------|----------------| +| A small change | a short one: what you already covered together, written down | +| A project | the full written design (below) | + +Short or full, the design doc has an **Approaches** part: the two or +three ways you considered building it, what each trades off, which one +they picked, and why. They pick in the conversation, before you write +the doc. + +If it grows mid-task, stop, say so, and step up a size. + +If this is really several independent projects, say so, agree on an +order, and brainstorm them one at a time. + +## The Written Design + +Write a plain document a talented builder in the domain could plan +from without going back to your human partner. Cover: + +- intent and the why +- goals, non-goals, anti-goals +- constraints +- approaches (above) +- the parts of the how they decided, as they decided them +- what's left to the builder + +For a small change, the short design covers the same points in a line +or two each and goes straight to your human partner, without the +builder check. + +**Where it goes:** in a software repo, +`docs/superpowers/specs/YYYY-MM-DD--design.md`, committed. +Otherwise, pick a sensible place and say where. Your human partner's +preferences override both. + +**Builder check** (for a project): dispatch a fresh subagent with +`builder-check-prompt.md` in this directory. When you tell your human +partner you're writing it up, say a reviewer will read it first and you +may come back once with a few questions. Don't hand over the document +until the check is back. Sort what the builder asks into three piles: + +- **Answered already** by the conversation or the project: put the answer + in the document. +- **Minor:** a sensible builder could settle it without changing what gets + built. Settle it yourself and mark it in the document as your call. +- **Theirs:** only your human partner can answer it, and the answer changes + what gets built. + +Bring the "theirs" pile in one message, most important first. Update the +document with their answers, then hand it over with a short list of the +calls you made so they can check those while reading. That's the only +round of questions the check produces. Without a subagent tool, read the +document as that builder yourself. -Before taking any implementation action, including invoking an -implementation skill, writing product code, scaffolding, installing -product dependencies, or creating an external project, complete the -selected path's prerequisites: - -- Spike: the human partner approves the question and probe. -- Bounded: the human partner approves the short in-chat design. -- Architectural: the human partner reviews and approves the written spec, - then reviews the written implementation plan and selects its execution - method. Conversational design approval only permits writing the spec; - written-spec approval only permits invoking writing-plans. - -A reply approves the stage actually presented. Approval of an idea or -feature scope does not approve artifacts that do not exist yet. Resume -at the earliest incomplete stage; do not turn one approval into permission -to skip the rest of the selected path. Read-only project exploration is -allowed while those prerequisites remain incomplete. +Nothing gets built until your human partner approves the design doc: +the short one for a small change, the full one for a project. +Building includes writing product code, scaffolding, installing +dependencies, creating projects, and invoking an implementation skill. +Reading and exploring are fine. + +Two exceptions. If they opted out of the questions, the skill is done +and the gate goes with it. A spike they said yes to may be built; it +stays labeled throwaway, and keeping what it produced is a new request +that comes back through this gate. + +After the design doc is approved, the next step is the plan: invoke +superpowers:writing-plans and no other skill. -## Three Paths - -Before your first question, classify the request and say the -classification out loud — "this looks bounded, so I'll present a short -design here rather than write a spec" — so your human partner can -override it: - -- **Spike** — a feasibility question ("can we...", "is it possible...", - "quick and dirty is fine") whose output is an answer, not code you - keep. Present the question and what you'll try in 2-3 sentences, get - a nod, then find out as cheaply as correctness allows. No design - doc, no spec file. Report findings as a recommendation; anything you - built stays labeled throwaway. -- **Bounded** — a well-scoped change to code that already exists in - this repo: a new flag, a small endpoint, a one-file fix. - Understanding the kind of app is not enough — bounded means the flow - you are changing is already here to read. If there is no existing - flow to change, the task is not bounded. Ask the clarifying - questions that matter, present a short design IN CHAT (a few - sentences to a few short paragraphs), and STOP. Implementation - starts only after your human partner says yes to that design — a - bounded task's approval is as hard a gate as an architectural - one. No spec file, no implementation plan document. -- **Architectural** — new projects, new subsystems, changes that - restructure how components fit together or alter interfaces others - depend on. Follow the full process: questions, approaches, sectioned - design, written spec, then the writing-plans skill. - -When in doubt between two paths, take the heavier one. The ratchet is -one-way: hidden complexity discovered mid-task upgrades the path — -stop, say so, and step up. Nothing downgrades mid-task. - -## Anti-Pattern: "Too Simple To Need Approval" - -Every path ends with your human partner approving the required design -before implementation. A bounded change may need only two sentences in -chat. A new todo-list project is architectural and requires the written -spec and planning handoffs. Scale the artifact to the selected path; -complete that path's reviews before implementation. - ## Red Flags | Thought | Reality | |---------|---------| -| "This is too simple to need a design" | Follow the selected path: a bounded change gets a short chat design; an architectural change gets the written spec and planning handoffs. | -| "I'll call it bounded and skip the spec" | Reaching for a label to skip work IS the doubt — take the heavier path. | -| "It's bounded and the design is obvious — I'll start while they read it" | The gate is the approval, not the design's length. Present, then stop until you hear yes. | -| "I understand this kind of app, so it's bounded" | Bounded measures the repo, not your familiarity. A new project has no existing flow — it is architectural. | -| "The spike works, so I'll keep the code" | A spike's output is an answer. Keeping the code is a new request — classify it. | -| "It grew, but I'm almost done — no need to re-classify" | Hidden complexity upgrades the path mid-task. Stop and say so. | -| "They approved the spike, so the follow-up change is approved too" | Each task gets its own classification and its own approval. | - -## Checklist - -Classify first, announce the path, then create a task for each item on -your path and complete them in order. - -**Spike:** -1. **Explore project context** — enough to frame the probe -2. **Present question + probe plan** — 2-3 sentences -3. **Get approval** — a nod is enough -4. **Investigate** — as cheaply as correctness allows -5. **Report findings** — a recommendation; label anything built as throwaway - -**Bounded:** -1. **Explore project context** — check files, docs, recent commits -2. **Ask clarifying questions** — one at a time, the ones that matter -3. **Present short design in chat** — approach, files touched, testing -4. **Get approval** — STOP and wait for an explicit yes; presenting the design and starting in the same breath is skipping the gate -5. **Implement** — proceed with the normal development workflow (TDD applies); no plan document - -**Architectural:** -1. **Explore project context** — check files, docs, recent commits -2. **Offer the visual companion just-in-time** — NOT upfront. The first time a question would genuinely be clearer shown than described, offer it then (its own message); on approval its browser tab opens for you. If no visual question ever arises, never offer it. See the Visual Companion section below. -3. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria -4. **Propose 2-3 approaches** — with trade-offs and your recommendation -5. **Present design** — in sections scaled to their complexity, get user approval after each section -6. **Write design doc** — save to `docs/superpowers/specs/YYYY-MM-DD--design.md` and commit -7. **Spec self-review** — quick inline check for placeholders, contradictions, ambiguity, scope (see below) -8. **User reviews written spec** — ask user to review the spec file before proceeding -9. **Transition to implementation** — invoke writing-plans skill to create implementation plan - -## Process Flow - -```dot -digraph brainstorming { - "Classify: spike / bounded / architectural" [shape=diamond]; - "Present question + probe (2-3 sentences)" [shape=box]; - "Ask clarifying questions (bounded)" [shape=box]; - "Present short design in chat" [shape=box]; - "Human approves?" [shape=diamond]; - "Investigate; report recommendation" [shape=doublecircle]; - "Implement via normal workflow (no plan doc)" [shape=doublecircle]; - "Explore project context" [shape=box]; - "Ask clarifying questions" [shape=box]; - "Propose 2-3 approaches" [shape=box]; - "Present design sections" [shape=box]; - "User approves design?" [shape=diamond]; - "Write design doc" [shape=box]; - "Spec self-review\n(fix inline)" [shape=box]; - "User reviews spec?" [shape=diamond]; - "Invoke writing-plans skill" [shape=doublecircle]; - "Hidden complexity? Upgrade path" [shape=box]; - - "Classify: spike / bounded / architectural" -> "Present question + probe (2-3 sentences)" [label="spike"]; - "Classify: spike / bounded / architectural" -> "Ask clarifying questions (bounded)" [label="bounded"]; - "Classify: spike / bounded / architectural" -> "Explore project context" [label="architectural"]; - "Present question + probe (2-3 sentences)" -> "Human approves?"; - "Ask clarifying questions (bounded)" -> "Present short design in chat"; - "Present short design in chat" -> "Human approves?"; - "Human approves?" -> "Investigate; report recommendation" [label="spike: yes"]; - "Human approves?" -> "Implement via normal workflow (no plan doc)" [label="bounded: yes"]; - "Hidden complexity? Upgrade path" -> "Classify: spike / bounded / architectural"; - "Explore project context" -> "Ask clarifying questions"; - "Ask clarifying questions" -> "Propose 2-3 approaches"; - "Propose 2-3 approaches" -> "Present design sections"; - "Present design sections" -> "User approves design?"; - "User approves design?" -> "Present design sections" [label="no, revise"]; - "User approves design?" -> "Write design doc" [label="yes"]; - "Write design doc" -> "Spec self-review\n(fix inline)"; - "Spec self-review\n(fix inline)" -> "User reviews spec?"; - "User reviews spec?" -> "Write design doc" [label="changes requested"]; - "User reviews spec?" -> "Invoke writing-plans skill" [label="approved"]; -} -``` - -**Terminal states are path-bound.** Architectural: the ONLY skill you -invoke after brainstorming is writing-plans — never frontend-design, -mcp-builder, or any other implementation skill. Bounded: after -approval, implementation proceeds directly through the normal -development workflow; no plan document. Spike: the terminal state is a -reported recommendation. - -## The Process - -The subsections below serve the bounded and architectural paths (a -spike stops at "present the probe, get a nod"). Sections from -**Exploring approaches** onward are architectural-path depth — for -bounded work, context plus a few questions plus a short in-chat design -is the whole process. - -**Understanding the idea:** - -- Check out the current project state first (files, docs, recent commits) -- Before asking detailed questions, assess scope: if the request describes multiple independent subsystems (e.g., "build a platform with chat, file storage, billing, and analytics"), flag this immediately. Don't spend questions refining details of a project that needs to be decomposed first. -- If the project is too large for a single spec, help the user decompose into sub-projects: what are the independent pieces, how do they relate, what order should they be built? Then brainstorm the first sub-project through the normal design flow. Each sub-project gets its own spec → plan → implementation cycle. -- For appropriately-scoped projects, ask questions one at a time to refine the idea -- Prefer multiple choice questions when possible, but open-ended is fine too -- Only one question per message - if a topic needs more exploration, break it into multiple questions -- Focus on understanding: purpose, constraints, success criteria - -**Exploring approaches:** - -- Propose 2-3 different approaches with trade-offs -- Present options conversationally with your recommendation and reasoning -- Lead with your recommended option and explain why -- YAGNI ruthlessly - remove unnecessary features from every approach and design - -**Presenting the design:** - -- Once you believe you understand what you're building, present the design -- Scale each section to its complexity: a few sentences if straightforward, up to 200-300 words if nuanced -- Ask after each section whether it looks right so far -- Cover: architecture, components, data flow, error handling, testing -- Be ready to go back and clarify if something doesn't make sense - -**Design for isolation and clarity:** - -- Break the system into smaller units that each have one clear purpose, communicate through well-defined interfaces, and can be understood and tested independently -- For each unit, you should be able to answer: what does it do, how do you use it, and what does it depend on? -- Can someone understand what a unit does without reading its internals? Can you change the internals without breaking consumers? If not, the boundaries need work. -- Smaller, well-bounded units are also easier for you to work with - you reason better about code you can hold in context at once, and your edits are more reliable when files are focused. When a file grows large, that's often a signal that it's doing too much. - -**Working in existing codebases:** - -- Explore the current structure before proposing changes. Follow existing patterns. -- Where existing code has problems that affect the work (e.g., a file that's grown too large, unclear boundaries, tangled responsibilities), include targeted improvements as part of the design - the way a good developer improves code they're working in. -- Don't propose unrelated refactoring. Stay focused on what serves the current goal. - -## After the Design (architectural path) - -**Documentation:** - -- Write the validated design (spec) to `docs/superpowers/specs/YYYY-MM-DD--design.md` - - (User preferences for spec location override this default) -- Use elements-of-style:writing-clearly-and-concisely skill if available -- Commit the design document to git - -**Spec Self-Review:** -After writing the spec document, look at it with fresh eyes: - -1. **Placeholder scan:** Any "TBD", "TODO", incomplete sections, or vague requirements? Fix them. -2. **Internal consistency:** Do any sections contradict each other? Does the architecture match the feature descriptions? -3. **Scope check:** Is this focused enough for a single implementation plan, or does it need decomposition? -4. **Ambiguity check:** Could any requirement be interpreted two different ways? If so, pick one and make it explicit. - -Fix any issues inline. No need to re-review — just fix and move on. - -**User Review Gate:** -After the spec review loop passes, ask the user to review the written spec before proceeding: - -> "Spec written and committed to ``. Please review it and let me know if you want to make any changes before we start writing out the implementation plan." - -Wait for the user's response. If they request changes, make them and re-run the spec review loop. Only proceed once the user approves. - -**Implementation:** - -- Invoke the writing-plans skill to create a detailed implementation plan -- Do NOT invoke any other skill. writing-plans is the next step. +| "I'll offer a menu of what they might want" | Options steer. Ask them to describe it first. | +| "I'll fill in sensible defaults" | Good. Say them out loud. A default they never heard is one they couldn't reject. | +| "I've answered their questions, so I'll write the doc" | Lay out the approaches first, in a message of their own (or, when nothing was open from the start, in your first message's short design). | +| "I should explain the process first" | Ask your question. The process shows itself. | +| "They said 'sounds good' to the idea" | Approval covers what you showed them. A design doc you haven't written isn't approved. | +| "The spike works, I'll keep building on it" | Keeping it is a new request. Back through the gate. | ## Visual Companion -A browser-based companion for showing mockups, diagrams, and visual options during brainstorming. Available as a tool — not a mode. Accepting the companion means it's available for questions that benefit from visual treatment; it does NOT mean every question goes through the browser. - -**Offering the companion (just-in-time):** Do NOT offer it upfront. Wait until a question would genuinely be clearer shown than told — a real mockup / layout / diagram question, not merely a UI *topic*. The first time that happens, offer it then, as its own message: -> "This next part might be easier if I show you — I can put together mockups, diagrams, and comparisons in a browser tab as we go. It's still new and can be token-intensive. Want me to? I'll open it for you." +A browser tab for showing mockups, diagrams, and prototypes. It's a +tool, not a mode: accepting it doesn't send every question to the +browser. -**This offer MUST be its own message.** Only the offer — no clarifying question, summary, or other content. Wait for the user's response. If they accept, start the server with `--open` so their browser opens to the first screen automatically. If they decline, continue text-only and don't offer again unless they raise it. +**Offer it just-in-time.** Offer it the first time one of the moments +above comes up, never upfront. The offer is its own message with nothing +else in it: -**Per-question decision:** Even after the user accepts, decide FOR EACH QUESTION whether to use the browser or the terminal. The test: **would the user understand this better by seeing it than reading it?** +> "This might be easier if I show you. Want me to open a browser tab with some mockups?" -- **Use the browser** for content that IS visual — mockups, wireframes, layout comparisons, architecture diagrams, side-by-side visual designs -- **Use the terminal** for content that is text — requirements questions, conceptual choices, tradeoff lists, A/B/C/D text options, scope decisions +If they decline, stay in text and don't offer again unless they raise it. -A question about a UI topic is not automatically a visual question. "What does personality mean in this context?" is a conceptual question — use the terminal. "Which wizard layout works better?" is a visual question — use the browser. +**Per question, ask: would they understand this better by seeing it?** +Mockups, layouts, diagrams, and side-by-side designs go in the browser. +Requirements, scope, tradeoffs, and conceptual choices stay in text. A +question about a UI topic isn't automatically a visual question. -If they agree to the companion, read the detailed guide before proceeding: -`skills/brainstorming/visual-companion.md` +If they accept, read `visual-companion.md` in this directory before +starting the server. diff --git a/external/basic/brainstorming/builder-check-prompt.md b/external/basic/brainstorming/builder-check-prompt.md new file mode 100644 index 0000000..136e9c1 --- /dev/null +++ b/external/basic/brainstorming/builder-check-prompt.md @@ -0,0 +1,23 @@ +# Builder Check Prompt + +Dispatch a fresh general-purpose subagent with this prompt once the +written design exists. Fill in the domain and the document path. + +``` +You are a talented [DOMAIN, e.g. "software engineer", "event planner", +"bakery consultant"]. You've been handed a design document and asked to +put together a plan to build what it describes. You will not be able to +talk to the person who wrote it. + +Read [DOCUMENT_PATH]. + +Report only the questions you'd still need answered before you could plan +this well: places where you'd have to guess and a wrong guess would +matter. For each, give the question and why the answer changes the plan. +Rank them by importance. + +Skip anything the document explicitly leaves to the builder, and anything +you could reasonably decide yourself. Skip nitpicks about wording. + +If nothing important is left, say "nothing important left". +``` diff --git a/external/basic/brainstorming/scripts/server.cjs b/external/basic/brainstorming/scripts/server.cjs index a828b35..fdc7961 100644 --- a/external/basic/brainstorming/scripts/server.cjs +++ b/external/basic/brainstorming/scripts/server.cjs @@ -261,7 +261,7 @@ function isFullDocument(html) { } function wrapInFrame(content) { - return renderBranding(frameTemplate).replace('', content); + return renderBranding(frameTemplate).replace('', () => content); } function getNewestScreen() { diff --git a/external/basic/brainstorming/scripts/start-server.sh b/external/basic/brainstorming/scripts/start-server.sh index 016a8e4..f6d538a 100755 --- a/external/basic/brainstorming/scripts/start-server.sh +++ b/external/basic/brainstorming/scripts/start-server.sh @@ -114,6 +114,13 @@ umask 077 SESSION_ID="$$-$(date +%s)" if [[ -n "$PROJECT_DIR" ]]; then + # Make the project dir absolute now: the script cds into its own directory + # before starting the server, which would re-root a relative path there. + # mkdir -p keeps the existing behavior of creating a missing project dir. + if ! PROJECT_DIR="$(mkdir -p -- "$PROJECT_DIR" && CDPATH='' cd -- "$PROJECT_DIR" && pwd)"; then + echo "{\"error\": \"Cannot create or enter --project-dir\"}" + exit 1 + fi SESSION_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}" # Persist the bound port and key per project so a restart reuses them and an # already-open browser tab reconnects to the same URL with a valid cookie. diff --git a/external/basic/brainstorming/spec-document-reviewer-prompt.md b/external/basic/brainstorming/spec-document-reviewer-prompt.md deleted file mode 100644 index 6099312..0000000 --- a/external/basic/brainstorming/spec-document-reviewer-prompt.md +++ /dev/null @@ -1,49 +0,0 @@ -# Spec Document Reviewer Prompt Template - -Use this template when dispatching a spec document reviewer subagent. - -**Purpose:** Verify the spec is complete, consistent, and ready for implementation planning. - -**Dispatch after:** Spec document is written to docs/superpowers/specs/ - -``` -Subagent (general-purpose): - description: "Review spec document" - prompt: | - You are a spec document reviewer. Verify this spec is complete and ready for planning. - - **Spec to review:** [SPEC_FILE_PATH] - - ## What to Check - - | Category | What to Look For | - |----------|------------------| - | Completeness | TODOs, placeholders, "TBD", incomplete sections | - | Consistency | Internal contradictions, conflicting requirements | - | Clarity | Requirements ambiguous enough to cause someone to build the wrong thing | - | Scope | Focused enough for a single plan — not covering multiple independent subsystems | - | YAGNI | Unrequested features, over-engineering | - - ## Calibration - - **Only flag issues that would cause real problems during implementation planning.** - A missing section, a contradiction, or a requirement so ambiguous it could be - interpreted two different ways — those are issues. Minor wording improvements, - stylistic preferences, and "sections less detailed than others" are not. - - Approve unless there are serious gaps that would lead to a flawed plan. - - ## Output Format - - ## Spec Review - - **Status:** Approved | Issues Found - - **Issues (if any):** - - [Section X]: [specific issue] - [why it matters for planning] - - **Recommendations (advisory, do not block approval):** - - [suggestions for improvement] -``` - -**Reviewer returns:** Status, Issues (if any), Recommendations diff --git a/external/basic/firecrawl-search/SKILL.md b/external/basic/firecrawl-search/SKILL.md index 7831635..fccbeb6 100644 --- a/external/basic/firecrawl-search/SKILL.md +++ b/external/basic/firecrawl-search/SKILL.md @@ -1,7 +1,6 @@ --- name: firecrawl-search -description: | - Web search with full page content. Use when no URL is known: finding sources, articles, or news. For papers use firecrawl-research-index; for library, API, error, or bug questions use firecrawl-developer-index. +description: Find web sources with query-relevant page excerpts and optional full-page content, and discover workflows, data APIs, and indexes. Use for web research or finding structured records, listings, transcripts, and datasets. Supports semantic tool discovery, domain matching, and progressive catalogue browsing. allowed-tools: - Bash(firecrawl *) - Bash(npx firecrawl-cli *) @@ -9,7 +8,9 @@ allowed-tools: # firecrawl search -Search naturally using the user’s actual question. In the Alexandria beta, default search returns web results plus relevant Alexandria tools, with optional web content scraping. +Search naturally using the user’s actual question. Default search returns web results plus relevant Alexandria tools, with optional web content scraping. + +For structured records, filterable listings, transcripts, or datasets, first check `firecrawl search alexandria ''` for a suitable workflow or data provider. For a known website, use `firecrawl find-tools `. Inspect a selected contract with `firecrawl list --pretty` before executing it through `scrape`; reuse a complete contract already returned by discovery. If no suitable tool exists, continue with web search or Agent. Use ordinary `search` for web research and URL `scrape` for a known page. ## Quick start @@ -24,28 +25,74 @@ firecrawl search "your query" --scrape -o .firecrawl/scraped.json --json firecrawl search "your query" --sources news --tbs qdr:d -o .firecrawl/news.json --json ``` -Run `firecrawl search --help` for the full option list. +Use `firecrawl search --help` for search options, `firecrawl list --help` for contract browsing, and `firecrawl scrape --help` for execution options. + +`--categories developer` searches an index of public repositories, GitHub issues, merged pull requests, repository READMEs, and curated documentation sites. `--categories gov` searches US federal, state, and local government legal and regulatory sources and cannot be combined with other categories. `--categories research` is a website filter, not the paper index. Dedicated skills: [firecrawl-developer-index](../firecrawl-developer-index/SKILL.md) and [firecrawl-research-index](../firecrawl-research-index/SKILL.md). + +## Government Index + +For US legal or regulatory source discovery, use `firecrawl gov "" --limit 10`. Use `--limit` for result count; `-k` is the API-key option. `search --categories gov` also uses `--limit` and returns results in `data.web`; run other categories separately rather than combining them with `gov`. + +`firecrawl gov` also takes filters; `search --categories gov` does not. Narrow with `--jurisdiction us-ca`, `--court ca9`, `--court-type state-supreme`, `--cites "42 U.S.C. § 1983"`, or `--domain irs.gov`; see `firecrawl gov --help` for values. Filters keep only pages the index has labeled, so a filtered search can leave out relevant pages, and no results does not mean none exist. Court labels are most complete for federal courts and case-law collections. + +Read a supplied source URL directly with `scrape`. Before citing current governing law, verify the source's issuer, jurisdiction, enacted/effective status, and version. Bill text, agency guidance, historical versions, and third-party reproductions need that verification too. Ask for a missing jurisdiction before selecting a rule; use general search for non-US questions or missing coverage. No hits does not prove that no applicable law exists. + +**Done when:** relevant results have been inspected, per-call errors and empty results have been checked, the request has been answered with source links, and feedback is sent within the time window unless opted out. + +## Go beyond page content with Alexandria + +Alexandria is a catalogue of ready-made website workflows, API providers, and specialized indexes. Depending on the tool, it can return structured records, detailed listings, financial data, company information, research, or public records that a search snippet or single scraped page does not contain. Discover current coverage rather than assuming a provider or capability exists. + +- **Semantic discovery** matches the meaning of the user's question to tool capabilities, even when no relevant provider website appears in the web results. Use `firecrawl search alexandria ''` when you specifically need tools. +- **Domain matching** surfaces tools associated with websites in the web results. A matched tool may retrieve richer details, related records, or structured collections beyond the linked page. Domain matching signals relevance, not proof that the tool covers the requested fields or market. +- **Combined search** uses both paths alongside web results by default: `firecrawl search ''`. Use the web result when sufficient; inspect a matching tool when it offers a more direct route to the required data. -`--categories developer` weighs the developer index beside ordinary web results in this same call (no passage control, no index filters). `--categories research` is a website filter, not the paper index. Dedicated skills: [firecrawl-developer-index](../firecrawl-developer-index/SKILL.md) and [firecrawl-research-index](../firecrawl-research-index/SKILL.md). +### Inspect before execution -**Done when:** results are saved under `.firecrawl/`, verified non-empty, processed for the request, and one feedback event is sent within the time window (unless opted out). +With an API key, search always includes Alexandria: it defaults to `web,alexandria` with domain-tool matching on, and `--sources` without `alexandria` (for example `--sources news`) gets it added. Preserve the user's location, marketplace, and constraints in the query; do not turn normal research into an artificial tool-discovery query. Inspect `data.web` and `data.tools` from the same response. -## Alexandria in normal search +Search returns compact tool matches by default: only `provider`, `capability`, and `description`. A match is not executed data. Select a candidate, then run `firecrawl list --pretty` with its provider and capability IDs to read the contract's inputs, coverage, and access requirements. -The beta defaults to `web,alexandria` with domain-tool matching on. Preserve the user's location, marketplace, and constraints in the query; do not turn normal research into an artificial tool-discovery query. Inspect `data.web` and `data.tools` from the same response. +Use `--tool-detail summary --json` for discovery metadata and navigation; inspect the selected contract with `list` before execution. Use `--tool-detail full --json` to receive contracts directly in search results and reuse them without another inspection call. Prefer full when several related contracts will be needed immediately. Displayed pricing is informational, not an extra confirmation gate. -A tool match is not executed data. If it fits the task, read its inputs, coverage, `creditsCost`/`perRecord`, and access requirements in the JSON. Execute it with `firecrawl scrape --alexandria --options ''`. All provider execution goes through Scrape; `search --scrape` only fetches web result content, not provider tools. +After inspecting the contract, execute with `firecrawl scrape --options ''` (`--alexandria` remains supported). All provider execution goes through Scrape; `search --scrape` only fetches web result content, not provider tools. + +Use `list` for category/provider browsing and selected contracts. For a known website, `find-tools ` discovers associated tools without executing them. Run `firecrawl find-tools --help` for advanced catalogue selectors; avoid broad expansion unless the task needs it. + +If no returned tool covers the country/market/segment or required inputs, continue with ordinary web results. Do not exhaust the catalogue or pay for adjacent tools just to probe coverage. With an API key, `--no-domain-tools` turns off domain matches only and semantic tool matches stay on; keyless searches return web results only. + +For Alexandria feedback about a provider result or coverage gap (each refunds 1 credit, up to 10 per website and 100 per team each UTC day), see [firecrawl-alexandria](../firecrawl-alexandria/SKILL.md). + +## Progressive discovery and output handling + +```bash +# Web + domain matching + semantic tools +firecrawl search '' + +# Semantic tools only +firecrawl search alexandria '' + +# Categories → providers → tools → contract +firecrawl list +firecrawl list --category +firecrawl list +firecrawl list --pretty + +# Execute a tool +firecrawl scrape / --options '' +``` -Use `find-tools` only for an explicitly requested tool set or a missing contract. It runs the `firecrawl/find-tools` meta tool through Scrape and never executes the tools it discovers. It accepts URLs or catalogue selectors; for “tools that can do X,” first use `search "X" --sources alexandria`, then narrow the returned providers with `find-tools --options '{"providers":[""],"level":"tools","limit":100}'`. +Default search combines web results, domain matches and semantic tools; `search alexandria` returns semantic tool matches only. Read the selected contract instead of expanding the entire catalogue. Tool discovery is not execution. -If no returned tool covers the country/market/segment or required inputs, continue with ordinary web results. Do not exhaust the catalogue or pay for adjacent tools just to probe coverage. `--sources web` explicitly opts out of Alexandria; `--sources web --domain-tools` retains domain matches only. +Keep large search responses in `--json -o` output and select the relevant results. If a subsequent provider execution or URL scrape exceeds the agent's output limit, use its retained ID with the [remote Bash recovery instructions](../firecrawl-scrape/references/large-results.md). Search request IDs are not supported Bash inputs. Do not blindly rerun a successful provider because the client could not display its result. ## Tips -- **`--highlights` on by default:** results are query-relevant excerpts, not full-page snippets. Use `--no-highlights` for the original snippets. +- **`--highlights` on by default:** results are query-relevant excerpts from the page. Use `--no-highlights` for the original snippets. - **`--scrape` fetches full content** — reuse that content instead of re-scraping result URLs. This saves credits and avoids redundant fetches. -- Always write results to `.firecrawl/` with `-o` to avoid context window bloat. -- Use `jq` to extract URLs or titles: `jq -r '.data.web[].url' .firecrawl/search.json` +- For large results, use `-o` and bounded local reads when a filesystem is available. Do not dump the full response into context. +- Use `jq` to extract URLs **with their source and 1-indexed position** (you'll need both for feedback): `jq -r '.data | to_entries[] | select(.key as $k | ["web","images","news"] | index($k)) | .key as $s | .value | to_entries[] | "\($s):\(.key + 1)\t\(.value.url // "-")"' .firecrawl/search.json` + Only those three groups are addressable by `--valuable-results`; `data.tools` and any other key are skipped, and an image or news result with no `url` prints `-` — address it by its position anyway. - Naming convention: `.firecrawl/search-{query}.json` or `.firecrawl/search-{query}-scraped.json` ## Send feedback after every search (refunds 1 credit) @@ -58,9 +105,10 @@ Search costs 2 credits. After you've actually used the results (or decided they - **Time window:** must be sent within ~2 minutes of the search. Late feedback is rejected. - **`--missing-content` is the most important field.** It's a list of _specific pieces_ of content you expected but did not find. One topic per entry, each in its own string. These aggregate across teams and tell us what to index next. +- **`--valuable-results` marks which results were useful.** Results come back grouped (`data.web`, `data.images`, `data.news`) and **each group is numbered from 1 independently**, so every entry is `source:position` — `web:1` and `news:1` are two different results. **Be exhaustive** — list every result that was actually useful; unlisted results are treated as not useful, so a partial list corrupts the signal. Reserve `--valuable-sources` for useful URLs that were NOT among the returned results (e.g. a page you found by following a result's link) — never report the same result in both. - **Substantive content required** (zero-effort feedback is rejected with HTTP 400): - - `good` → must include at least one `--valuable-sources` entry. - - `partial` → must include `--valuable-sources` or `--missing-content`. + - `good` → must include `--valuable-results` or at least one `--valuable-sources` entry. + - `partial` → must include `--valuable-results`, `--valuable-sources`, or `--missing-content`. - `bad` → must include `--missing-content` or `--query-suggestions`. - **Daily refund cap (per team, per UTC day, default 100 credits).** Once your team has been refunded 100 credits today, further submissions still record feedback but no longer refund credits. The response includes `creditsRefundedToday` / `dailyRefundCap` / `dailyCapReached`. **When `dailyCapReached: true`, stop calling `search-feedback` for the rest of the UTC day** — it won't refund anything and you're wasting bandwidth. - **Idempotent:** re-submitting for the same search id returns success but no extra refund. @@ -69,13 +117,15 @@ Search costs 2 credits. After you've actually used the results (or decided they Verify the search returned results before reading its `id`. Zero-result searches write no output file, so the file may be missing — or left over from an earlier search. The guard below skips feedback when the file is missing or has zero results; call `search-feedback` only inside it: ```bash -# Send once per search. Rate honestly and replace the placeholder with the -# rating that matches what actually happened. The two fields shown -# satisfy the substantive-content rule for every rating. +# Send once per search. Replace BOTH placeholders: the rating that matches +# what actually happened, and the exhaustive source:position list of the +# results that were genuinely useful (from the jq above). Never send the +# list below as-is -- marking results you did not use corrupts the signal. +# The two fields shown satisfy the substantive-content rule for every rating. if SEARCH_ID=$(jq -er 'select(any(.data[]; length > 0)) | .id' .firecrawl/search-react-hooks.json); then firecrawl search-feedback "$SEARCH_ID" \ --rating "" \ - --valuable-sources '[{"url":"https://react.dev/reference/react/hooks","reason":"Most authoritative"}]' \ + --valuable-results "" \ --missing-content '[{"topic":"useDeferredValue","description":"No example of useDeferredValue with Suspense"}]' \ --silent & fi diff --git a/external/develop/code-quality/clean-code/SKILL.md b/external/develop/code-quality/clean-code/SKILL.md index 9e7a61f..a9102c4 100644 --- a/external/develop/code-quality/clean-code/SKILL.md +++ b/external/develop/code-quality/clean-code/SKILL.md @@ -20,6 +20,8 @@ Use this skill when: - **Refactoring legacy code**: To identify and remove code smells. - **Improving team standards**: To align on industry-standard best practices. +These are design heuristics, not universal language rules or authority to change behavior. Follow the actual repository conventions; exceptions versus result types, nullability, argument counts and test cadence depend on the language and task. Preserve necessary rationale, security and license comments. Refactor only within scope and verify the relevant behavior. + ## 1. Meaningful Names - **Use Intention-Revealing Names**: `elapsedTimeInDays` instead of `d`. - **Avoid Disinformation**: Don't use `accountList` if it's actually a `Map`. diff --git a/external/develop/devops/cloudflare/SKILL.md b/external/develop/devops/cloudflare/SKILL.md index 76a37b0..bc4d301 100644 --- a/external/develop/devops/cloudflare/SKILL.md +++ b/external/develop/devops/cloudflare/SKILL.md @@ -1,12 +1,22 @@ --- name: cloudflare -description: Discover and choose Cloudflare products for apps, APIs, AI agents, storage, networking, and security. Use for architecture and product selection, including when the user describes a need without naming a Cloudflare product; then find the relevant skill or documentation. +description: Discover and choose Cloudflare products for apps, APIs, AI agents, storage, networking, and security. Use for architecture and product selection, including when the user describes a need without naming a Cloudflare product; then find the relevant skill or documentation. Also use when a Cloudflare-proxied site shows a Cloudflare error page, a `52x` status, a 1xxx error code, or a Cloudflare `403`, to decide whether to fix Cloudflare configuration or the origin. --- # Discover and build with Cloudflare Help agents discover what they can build with Cloudflare and choose the products that fit. Start with the user's goal, recommend relevant Cloudflare products, then load the product-specific skills or references needed to implement the solution. +## Choose the CLI + +Use the Cloudflare CLI (`cf`) unless the project already uses Wrangler or the user asks for Wrangler. + +- **The project has `wrangler.jsonc`, `wrangler.json`, or `wrangler.toml`, and no `cloudflare.config.ts`:** use Wrangler for development and deployment, and load the `wrangler` skill. `cf` resource commands work alongside it. Run `cf migrate` only when the user asks. +- **Every other project, including a new project or one with no Cloudflare configuration:** use `cf`. Load the `cf-cli` skill if it is installed; otherwise follow [Use cf with coding agents](https://developers.cloudflare.com/cf/agents/index.md). Do not install Wrangler or create a Wrangler configuration file. +- **A new web-framework project:** follow the `frameworks-on-cloudflare` skill. The `cf` docs do not yet cover creating framework projects, so it uses each framework's Workers guide. + +The product guidance below, and the documentation it links to, applies with either CLI. Many documentation pages show only Wrangler commands and configuration; in a `cf` project, do not run them or add a Wrangler configuration file. Use one CLI per project, apart from the `npx wrangler` commands the `cf-cli` skill lists as not yet supported by `cf`. Where guidance names a Wrangler command or file, find the `cf` equivalent with `cf cli search ""` or the [Wrangler to cf reference](https://developers.cloudflare.com/cf/wrangler/reference/index.md) instead of translating from memory. + ## Help the user find the right product - Actively surface Cloudflare products that solve the stated problem, even when the user has not named them. Explain the role each recommended product plays and why it fits. @@ -16,41 +26,45 @@ Help agents discover what they can build with Cloudflare and choose the products ## What are you trying to build? -**Recommend Workers and [Workers Static Assets](https://developers.cloudflare.com/workers/static-assets/) for new websites and applications, including static sites, SPAs, and full-stack apps.** Workers can do everything Pages can do, and is recommended for all new projects. Preserve existing Pages deployments during unrelated maintenance. +**Recommend Workers and [Workers Static Assets](https://developers.cloudflare.com/workers/static-assets/index.md) for new websites and applications, including static sites, SPAs, and full-stack apps.** Workers can do everything Pages can do, and is recommended for all new projects. Preserve existing Pages deployments during unrelated maintenance. -Find the row closest to the user's task. Products can appear in multiple rows, and a solution can combine products. Read the linked reference or docs before implementing; load named skills when installed. Local links open bundled references: start with the README, then follow configuration, API, pattern, or gotcha links as needed. If a named skill is unavailable, use the relevant product docs through the [Cloudflare directory](https://developers.cloudflare.com/directory/); sibling skills are optional. +Find the row closest to the user's task. Products can appear in multiple rows, and a solution can combine products. Read the linked reference or docs before implementing; load named skills when installed. Local links open bundled references: start with the README, then follow configuration, API, pattern, or gotcha links as needed. If a named skill is unavailable, use the relevant product docs through the [Cloudflare directory](https://developers.cloudflare.com/directory/index.md); sibling skills are optional. | What you need to do | Product or tool to consider | When to choose it | Skill or reference | | --- | --- | --- | --- | -| Choose the building blocks for an AI application | AI overview | Compare Cloudflare's AI services before choosing inference, retrieval, or agent tooling | [AI docs](https://developers.cloudflare.com/ai/) | -| Choose infrastructure for a customer-facing platform | Cloudflare for Platforms | Compare running customer code with serving an app on customer domains | [Platform overview](https://developers.cloudflare.com/cloudflare-for-platforms/) | -| Choose an approach to live audio and video | Realtime | Compare application SDKs, media infrastructure, and connectivity relays | [Realtime overview](https://developers.cloudflare.com/realtime/) | -| Start a Worker or framework project | C3 | Scaffold a project using the appropriate framework template | [C3](references/c3/README.md); `wrangler` skill | -| Build or deploy a Next.js app on Cloudflare | vinext + Workers | Use vinext rather than OpenNext for new projects | [nextjs-on-cloudflare skill](../nextjs-on-cloudflare/SKILL.md); [Next.js docs](https://developers.cloudflare.com/workers/framework-guides/web-apps/nextjs/) | +| Choose the building blocks for an AI application | AI overview | Compare Cloudflare's AI services before choosing inference, retrieval, or agent tooling | [AI docs](https://developers.cloudflare.com/ai/index.md) | +| Choose infrastructure for a customer-facing platform | Cloudflare for Platforms | Compare running customer code with serving an app on customer domains | [Platform overview](https://developers.cloudflare.com/cloudflare-for-platforms/index.md) | +| Choose an approach to live audio and video | Realtime | Compare application SDKs, media infrastructure, and connectivity relays | [Realtime overview](https://developers.cloudflare.com/realtime/index.md) | +| Start a Worker project | `cf` | Create a basic Worker with `cf init` | `cf-cli` skill; [First Worker](https://developers.cloudflare.com/cf/get-started/first-worker/index.md) | +| Build, set up, or migrate a framework app | Framework tooling + Workers | Follow the framework's Workers guide and its own agent guidance | `frameworks-on-cloudflare` skill; [Framework guides](https://developers.cloudflare.com/workers/framework-guides/index.md) | +| Build or deploy a Next.js app on Cloudflare | vinext + Workers | Use vinext rather than OpenNext for new projects | [nextjs-on-cloudflare skill](../nextjs-on-cloudflare/SKILL.md); [Next.js docs](https://developers.cloudflare.com/workers/framework-guides/web-apps/nextjs/index.md) | | Host a new static site, SPA, or full-stack app | Workers + Workers Static Assets | Serve site files and add server-side logic where needed | [Static Assets](references/static-assets/README.md); `workers-best-practices` skill | -| Build an API or handle webhooks | Workers | Run request handlers with access to Cloudflare services | `workers-best-practices` skill; [Workers docs](https://developers.cloudflare.com/workers/) | +| Build an API or handle webhooks | Workers | Run request handlers with access to Cloudflare services | `workers-best-practices` skill; [Workers docs](https://developers.cloudflare.com/workers/index.md) | +| Get a new or checked-out app running locally | Bindings, secrets, and local development | Connect the resources and values the app needs, then verify a real request | `cloudflare-app-bootstrap` skill; [Local development](https://developers.cloudflare.com/workers/local-development/index.md) | +| Control team, CI, or service-account access to Developer Platform resources | Roles, scopes, and permission policies | Choose the least-privilege role and a scope supported for the member, User Group, or API token | [Roles and permissions](https://developers.cloudflare.com/workers/authorization/index.md); `wrangler` skill for CLI access | | Maintain an existing Pages deployment | Pages + Pages Functions | Update an existing site or its server endpoints; use Workers for new projects | [Pages](references/pages/README.md); [Pages Functions](references/pages-functions/README.md) | -| Move a Pages project to Workers | Workers + Workers Static Assets | The task calls for migrating the hosting platform | [Pages migration guide](https://developers.cloudflare.com/workers/static-assets/migration-guides/migrate-from-pages/) | -| Let customers deploy code on your platform | Workers for Platforms | Run and manage customer Workers with per-customer controls | [Workers for Platforms](references/workers-for-platforms/README.md) | -| Let customers use their own domains with your app | Cloudflare for SaaS | Manage custom hostnames, TLS certificates, and origin routing; check hostname validation and apex-domain plan requirements. Combine with Workers for Platforms when customers also deploy code | [SaaS docs](https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/) | +| Move a Pages project to Workers | Workers + Workers Static Assets | The task calls for migrating the hosting platform | [Pages migration guide](https://developers.cloudflare.com/workers/static-assets/migration-guides/migrate-from-pages/index.md) | +| Let customers deploy code on your platform | Workers for Platforms | Run and manage customer Workers with per-customer controls | [Workers for Platforms docs](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/index.md) | +| Let customers use their own domains with your app | Cloudflare for SaaS | Manage custom hostnames, TLS certificates, and origin routing; check hostname validation and apex-domain plan requirements. Combine with Workers for Platforms when customers also deploy code | [SaaS docs](https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/index.md) | | Connect a Worker to storage or another service | Bindings | Give the Worker access to configured resources through its environment | [Bindings](references/bindings/README.md) | | Run containerized services or Linux software | Containers | The workload needs a container image or software outside the Workers runtime | [Containers](references/containers/README.md) | -| Execute generated or untrusted code, build Code Mode tools, or create on-demand previews | Dynamic Workers | Load code at runtime in isolated Workers; check bindings, egress controls, and resource limits. Choose Sandbox when execution needs Linux or shell tools | [Dynamic Workers docs](https://developers.cloudflare.com/dynamic-workers/) | -| Give an agent a shell, filesystem, or interactive development environment | Sandbox SDK | Code execution needs a Linux environment or container tools; inspect the package line first | `sandbox-next` for new or preview projects; `sandbox-stable` for existing stable apps; [Sandbox docs](https://developers.cloudflare.com/sandbox/) | -| Upgrade a stable Sandbox app to the preview API | Sandbox SDK | The user wants the stable-to-next migration | `sandbox-migrate-to-next` skill; [migration guide](https://developers.cloudflare.com/sandbox/1-0-preview/migrate/) | -| Coordinate chat rooms, games, collaborative documents, or bookings | Durable Objects | Operations need shared state and coordination per room, document, or entity | `durable-objects` skill; [Durable Objects docs](https://developers.cloudflare.com/durable-objects/) | -| Store and recover state inside a Durable Object | Durable Object storage | Choose storage APIs, transactions, and recovery for coordinated per-entity data | [DO storage](references/do-storage/README.md) | +| Execute generated or untrusted code, build Code Mode tools, or create on-demand previews | Dynamic Workers | Load code at runtime in isolated Workers; check bindings, egress controls, and resource limits. Choose Sandbox when execution needs Linux or shell tools | [Dynamic Workers docs](https://developers.cloudflare.com/dynamic-workers/index.md) | +| Give an agent a shell, filesystem, or interactive development environment | Sandbox SDK | Code execution needs a Linux environment or container tools; inspect the package line first | `sandbox-next` for new or preview projects; `sandbox-stable` for existing stable apps; [Sandbox docs](https://developers.cloudflare.com/sandbox/index.md) | +| Upgrade a stable Sandbox app to the preview API | Sandbox SDK | The user wants the stable-to-next migration | `sandbox-migrate-to-next` skill; [migration guide](https://developers.cloudflare.com/sandbox/sdk/migrate/index.md) | +| Coordinate chat rooms, games, collaborative documents, or bookings | Durable Objects | Operations need shared state and coordination per room, document, or entity | `durable-objects` skill; [Durable Objects docs](https://developers.cloudflare.com/durable-objects/index.md) | +| Store and recover state inside a Durable Object | Durable Object storage | Choose storage APIs, transactions, and recovery for coordinated per-entity data | `durable-objects` skill; [SQLite storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/index.md) | | Store application records and query them with SQL | D1 | Use a managed relational database; use Durable Objects when per-entity coordination is central | [D1](references/d1/README.md) | | Connect to an existing PostgreSQL or MySQL database | Hyperdrive | Keep the existing database and optimize connections from Workers | [Hyperdrive](references/hyperdrive/README.md) | | Distribute configuration or other key-value data | KV | Read-heavy key-value access fits the workload's consistency requirements | [KV](references/kv/README.md) | | Store uploads, downloads, or large objects | R2 | Store files by object key; pair with D1 when searchable metadata needs SQL | [R2](references/r2/README.md) | -| Store versioned file trees, agent checkpoints, or repositories | Artifacts | Files need versioning and Git-compatible access; currently closed beta, so confirm access before implementation | [Artifacts](references/artifacts/README.md) | -| Ingest event streams into a data lake | Pipelines | Transform and deliver streaming records into R2 | [Pipelines](references/pipelines/README.md) | -| Manage Iceberg tables in R2 | R2 Data Catalog | Organize tables for a data lake and compatible query engines | [R2 Data Catalog](references/r2-data-catalog/README.md) | -| Query a data lake with SQL | R2 SQL | Analyze data in R2 Data Catalog rather than transactional application records | [R2 SQL](references/r2-sql/README.md) | -| Cache application responses | Workers Cache | Default for application caching; check the patterns and limitations before choosing alternatives | [Workers Cache](https://developers.cloudflare.com/workers/cache/); see caching guidance below | -| Accelerate an existing website and control cached content | Cache/CDN | Configure caching for a proxied origin using Cache Rules, expiration settings, and purging | [Cache/CDN docs](https://developers.cloudflare.com/cache/) | -| Keep origin content in a persistent cache | Cache Reserve | Reduce origin fetches with persistent CDN cache storage | [Cache Reserve](references/cache-reserve/README.md) | +| Store versioned file trees, agent checkpoints, or repositories | Artifacts | Files need versioning and Git-compatible access; currently closed beta, so confirm access before implementation | [Artifacts docs](https://developers.cloudflare.com/artifacts/index.md) | +| Ingest event streams into R2 | Basin Pipelines | Transform and deliver streaming records into R2 | `basin` skill; [Basin Pipelines](https://developers.cloudflare.com/basin-pipelines/index.md) | +| Manage Iceberg tables in R2 | Basin Catalog | Organize tables for analytics and compatible query engines | `basin` skill; [Basin Catalog](https://developers.cloudflare.com/basin-catalog/index.md) | +| Query Iceberg tables with SQL | Basin SQL | Analyze tables in Basin Catalog | `basin` skill; [Basin SQL](https://developers.cloudflare.com/basin-sql/index.md) | +| Keep a durable event log with independent readers | K2 Streams | Produce records from Workers or HTTP, then consume with subscriptions | `k2` skill; [K2 docs](https://developers.cloudflare.com/k2/index.md) | +| Cache application responses | Workers Cache | Default for application caching; check the patterns and limitations before choosing alternatives | [Workers Cache](https://developers.cloudflare.com/workers/cache/index.md); see caching guidance below | +| Accelerate an existing website and control cached content | Cache/CDN | Configure caching for a proxied origin using Cache Rules, expiration settings, and purging | [Cache/CDN docs](https://developers.cloudflare.com/cache/index.md) | +| Keep origin content in a persistent cache | Cache Reserve | Reduce origin fetches with persistent CDN cache storage | [Cache Reserve docs](https://developers.cloudflare.com/cache/advanced-configuration/cache-reserve/index.md) | | Process jobs asynchronously or buffer bursts of work | Queues | Decouple producers and consumers; use Workflows for durable multi-step orchestration | [Queues](references/queues/README.md) | | Run a job that retries, waits, and resumes across steps | Workflows | Coordinate durable multi-step business processes | [Workflows](references/workflows/README.md) | | Start a Worker on a recurring schedule | Cron Triggers | Trigger scheduled work; combine with Queues or Workflows for the work itself | [Cron Triggers](references/cron-triggers/README.md) | @@ -58,72 +72,87 @@ Find the row closest to the user's task. Products can appear in multiple rows, a | Add managed search or answers over your content | AI Search | Use a managed retrieval-augmented generation pipeline | [AI Search](references/ai-search/README.md) | | Build custom semantic search or retrieval | Vectorize + Workers AI | Control embeddings, indexing, and retrieval rather than using a managed pipeline | [Vectorize](references/vectorize/README.md); [Workers AI](references/workers-ai/README.md) | | Observe and control requests to AI providers | AI Gateway | Add inference analytics, caching, and request controls | [AI Gateway](references/ai-gateway/README.md) | -| Build stateful agents with tools, scheduling, or chat | Agents SDK | Implement agent behavior on Cloudflare; add Dynamic Workers or Sandbox for the required execution runtime | `agents-sdk` skill; [Agents docs](https://developers.cloudflare.com/agents/) | +| Build stateful agents with tools, scheduling, or chat | Agents SDK | Implement agent behavior on Cloudflare; add Dynamic Workers or Sandbox for the required execution runtime | `agents-sdk` skill; [Agents docs](https://developers.cloudflare.com/agents/index.md) | | Build durable agents with TypeScript hooks | Flue | Use an open agent framework with Cloudflare and Node.js targets | [Flue](https://flueframework.com/); [getting started](https://flueframework.com/docs/guide/getting-started/); [Cloudflare target](https://flueframework.com/docs/guide/cloudflare-target/) | -| Expose tools through a remote MCP server | Workers + Agents SDK | Publish tools for MCP clients, with authentication appropriate to the service | `agents-sdk` skill, its `references/mcp.md`; [MCP docs](https://developers.cloudflare.com/agents/model-context-protocol/) | +| Expose tools through a remote MCP server | Workers + Agents SDK | Publish tools for MCP clients, with authentication appropriate to the service | `agents-sdk` skill, its `references/mcp.md`; [MCP docs](https://developers.cloudflare.com/agents/model-context-protocol/index.md) | | Automate browsers, take screenshots, or extract rendered pages | Browser Run | The task requires a browser rather than a plain HTTP request | [Browser Run](references/browser-rendering/README.md) | -| Connect a domain, configure DNS records, or troubleshoot resolution | DNS | Manage authoritative records and choose whether traffic is proxied through Cloudflare | [DNS docs](https://developers.cloudflare.com/dns/) | -| Configure HTTPS and certificates | SSL/TLS | Secure connections from visitors to Cloudflare and from Cloudflare to the origin | [SSL/TLS docs](https://developers.cloudflare.com/ssl/) | -| Distribute traffic across origins and fail over unhealthy servers | Load Balancing | Use health checks and traffic steering for multiple origin servers | [Load Balancing docs](https://developers.cloudflare.com/load-balancing/) | +| Connect a domain, configure DNS records, or troubleshoot resolution | DNS | Manage authoritative records and choose whether traffic is proxied through Cloudflare | [DNS docs](https://developers.cloudflare.com/dns/index.md) | +| Configure HTTPS and certificates | SSL/TLS | Secure connections from visitors to Cloudflare and from Cloudflare to the origin | [SSL/TLS docs](https://developers.cloudflare.com/ssl/index.md) | +| Distribute traffic across origins and fail over unhealthy servers | Load Balancing | Use health checks and traffic steering for multiple origin servers | [Load Balancing docs](https://developers.cloudflare.com/load-balancing/index.md) | | Connect an existing server to Cloudflare | Cloudflare Tunnel | Reach an origin without a publicly routable IP address | [Tunnel](references/tunnel/README.md) | -| Connect Workers to private services | Workers VPC | Access services in private networks from a Worker | [Workers VPC](references/workers-vpc/README.md) | -| Require employee login before accessing an internal app | Access | Put identity-based access policies in front of an internal application | `cloudflare-one` skill; [Access docs](https://developers.cloudflare.com/cloudflare-one/access-controls/) | -| Protect access to internal applications and networks | Cloudflare One | Apply identity and network access policies | `cloudflare-one` skill; [Cloudflare One docs](https://developers.cloudflare.com/cloudflare-one/) | -| Migrate existing access and network security configurations | Cloudflare One | The task is a supported migration to Cloudflare One | `cloudflare-one-migrations` skill; [Cloudflare One docs](https://developers.cloudflare.com/cloudflare-one/) | -| Proxy a TCP or UDP application | Spectrum | Protect and accelerate non-HTTP application traffic | [Spectrum](references/spectrum/README.md) | -| Connect a network directly to Cloudflare | Network Interconnect | Dedicated network connectivity is required | [Network Interconnect](references/network-interconnect/README.md) | -| Improve routing across the network | Argo Smart Routing | Optimize traffic paths to the origin | [Argo Smart Routing](references/argo-smart-routing/README.md) | -| Reduce Worker-to-backend latency | Smart Placement | Place Worker execution closer to the backends it calls | [Smart Placement](references/smart-placement/README.md) | -| Redirect URLs, rewrite paths or headers, or change origin routing | Rules | Use Redirect, Transform, or Origin Rules when configuration can express the required behavior | [Rules docs](https://developers.cloudflare.com/rules/) | -| Make small HTTP request or response changes | Snippets | Lightweight edge logic meets the need | [Snippets](references/snippets/README.md) | -| Protect forms from automated abuse | Turnstile | Add bot challenges and server-side token validation | `turnstile-spin` skill; [Turnstile docs](https://developers.cloudflare.com/turnstile/) | +| Connect Workers to private services | Workers VPC | Access services in private networks from a Worker | [Workers VPC docs](https://developers.cloudflare.com/workers-vpc/index.md) | +| Access a protected webpage or API from an agent, script, or crawler | Access service tokens | Authenticate an authorized non-browser client to an Access application | `cloudflare-one` skill; [Service token docs](https://developers.cloudflare.com/cloudflare-one/access-controls/service-credentials/service-tokens/index.md) | +| Require employee login before accessing an internal app | Access | Put identity-based access policies in front of an internal application | `cloudflare-one` skill; [Access docs](https://developers.cloudflare.com/cloudflare-one/access-controls/index.md) | +| Protect access to internal applications and networks | Cloudflare One | Apply identity and network access policies | `cloudflare-one` skill; [Cloudflare One docs](https://developers.cloudflare.com/cloudflare-one/index.md) | +| Transform permitted Gateway HTTP traffic with a Worker | Gateway Forward + Workers | Apply custom request or response logic to traffic passing through Gateway | [gateway-workers skill](../gateway-workers/SKILL.md) | +| Migrate existing access and network security configurations | Cloudflare One | The task is a supported migration to Cloudflare One | `cloudflare-one-migrations` skill; [Cloudflare One docs](https://developers.cloudflare.com/cloudflare-one/index.md) | +| Proxy a TCP or UDP application | Spectrum | Protect and accelerate non-HTTP application traffic | [Spectrum docs](https://developers.cloudflare.com/spectrum/index.md) | +| Connect a network directly to Cloudflare | Network Interconnect | Dedicated network connectivity is required | [Network Interconnect docs](https://developers.cloudflare.com/network-interconnect/index.md) | +| Improve routing across the network | Argo Smart Routing | Optimize traffic paths to the origin | [Argo Smart Routing docs](https://developers.cloudflare.com/argo-smart-routing/index.md) | +| Reduce Worker-to-backend latency | Workers placement | Use explicit region hints for known regional databases, or traffic-based Smart Placement | [Workers placement docs](https://developers.cloudflare.com/workers/configuration/placement/index.md) | +| Redirect URLs, rewrite paths or headers, or change origin routing | Rules | Use Redirect, Transform, or Origin Rules when configuration can express the required behavior | [Rules docs](https://developers.cloudflare.com/rules/index.md) | +| Make small HTTP request or response changes | Snippets | Lightweight edge logic meets the need | [Snippets docs](https://developers.cloudflare.com/rules/snippets/index.md) | +| Protect forms from automated abuse | Turnstile | Add bot challenges and server-side token validation | `turnstile-spin` skill; [Turnstile docs](https://developers.cloudflare.com/turnstile/index.md) | | Filter malicious web requests | WAF | Apply application-layer rules and managed protections | [WAF](references/waf/README.md) | -| Protect services from denial-of-service attacks | DDoS Protection | Mitigate attacks at the relevant network or application layer | [DDoS protection](references/ddos/README.md) | -| Detect and control automated traffic | Bot Management | Make request decisions based on bot detection | [Bot Management](references/bot-management/README.md) | -| Discover and protect API endpoints | API Shield | Apply API-specific protections and validation | [API Shield](references/api-shield/README.md) | -| Queue visitors during traffic spikes | Waiting Room | Control admission when application capacity is limited | [Waiting Room docs](https://developers.cloudflare.com/waiting-room/) | -| Store a Worker's API keys and credentials | Workers secrets | Bind secrets to a Worker without committing values to source | `wrangler` skill; [secrets docs](https://developers.cloudflare.com/workers/configuration/secrets/) | +| Protect services from denial-of-service attacks | DDoS Protection | Mitigate attacks at the relevant network or application layer | [DDoS protection docs](https://developers.cloudflare.com/ddos-protection/index.md) | +| Detect and control automated traffic | Bot Management | Make request decisions based on bot detection | [Bot Management docs](https://developers.cloudflare.com/bots/index.md) | +| Discover and protect API endpoints | API Shield | Apply API-specific protections and validation | [API Shield docs](https://developers.cloudflare.com/api-shield/index.md) | +| Queue visitors during traffic spikes | Waiting Room | Control admission when application capacity is limited | [Waiting Room docs](https://developers.cloudflare.com/waiting-room/index.md) | +| Store a Worker's API keys and credentials | Workers secrets | Bind secrets to a Worker without committing values to source | `wrangler` skill; [secrets docs](https://developers.cloudflare.com/workers/configuration/secrets/index.md) | | Share managed secrets across services | Secrets Store | Manage reusable account-level secrets | [Secrets Store](references/secrets-store/README.md) | -| Control where data is processed and stored | Data Localization Suite | Evaluate regional processing and storage controls against the actual requirements | [Data Localization docs](https://developers.cloudflare.com/data-localization/) | -| Prove a claim without identifying or tracking the user | Privacy Pass | Use privacy-preserving tokens in a supported integration | [Privacy Pass docs](https://developers.cloudflare.com/privacy-pass/) | +| Control where data is processed and stored | Data Localization Suite | Evaluate regional processing and storage controls against the actual requirements | [Data Localization docs](https://developers.cloudflare.com/data-localization/index.md) | +| Prove a claim without identifying or tracking the user | Privacy Pass | Use privacy-preserving tokens in a supported integration | [Privacy Pass docs](https://developers.cloudflare.com/privacy-pass/index.md) | | Store, resize, transform, and deliver images | Cloudflare Images | Use managed image processing and delivery | [Images](references/images/README.md) | | Encode, store, and deliver live or on-demand video | Stream | Use managed video infrastructure | [Stream](references/stream/README.md) | -| Build an audio/video calling application with SDKs | RealtimeKit | Use application-level SDKs for calls and meetings | [RealtimeKit](references/realtimekit/README.md) | -| Build custom real-time media infrastructure | Realtime SFU | Control the application while using a selective forwarding unit for media | [Realtime SFU](references/realtime-sfu/README.md) | -| Relay WebRTC connections through restrictive networks | TURN Service | Clients need a connectivity relay | [TURN](references/turn/README.md) | -| Deliver live media over QUIC | MoQ | Use the Media over QUIC protocol; check current compatibility and availability | [MoQ docs](https://developers.cloudflare.com/moq/) | -| Send transactional email | Email Service | Send application-generated messages | `cloudflare-email-service` skill; [Email Service docs](https://developers.cloudflare.com/email-service/) | -| Forward incoming email | Email Routing | Route addresses on a domain to destination mailboxes | [Email Routing](references/email-routing/README.md) | -| Process incoming email in code | Email Workers | Apply custom logic to inbound messages | [Email Workers](references/email-workers/README.md) | -| Manage third-party tags and scripts | Zaraz | Load and manage third-party tools through Cloudflare | [Zaraz](references/zaraz/README.md) | -| Run locally and manage resources from the CLI | Wrangler | Develop, configure, deploy, and inspect the intended account and environment | `wrangler` skill; [Wrangler docs](https://developers.cloudflare.com/workers/wrangler/) | -| Test Worker behavior before deployment | Workers testing tools | Choose runtime tests or integration tests for the affected behavior | [Testing docs](https://developers.cloudflare.com/workers/testing/); `durable-objects` skill for DO tests | +| Build an audio/video calling application with SDKs | RealtimeKit | Use application-level SDKs for calls and meetings | [RealtimeKit docs](https://developers.cloudflare.com/realtime/realtimekit/index.md) | +| Build custom real-time media infrastructure | Realtime SFU | Control the application while using a selective forwarding unit for media | [Realtime SFU docs](https://developers.cloudflare.com/realtime/sfu/index.md) | +| Relay WebRTC connections through restrictive networks | TURN Service | Clients need a connectivity relay | [TURN docs](https://developers.cloudflare.com/realtime/turn/index.md) | +| Deliver live media over QUIC | MoQ | Use the Media over QUIC protocol; check current compatibility and availability | [MoQ docs](https://developers.cloudflare.com/moq/index.md) | +| Send transactional email | Email Service | Send application-generated messages | `cloudflare-email-service` skill; [Email Service docs](https://developers.cloudflare.com/email-service/index.md) | +| Forward incoming email | Email Routing | Route addresses on a domain to destination mailboxes | `cloudflare-email-service` skill; [Route emails](https://developers.cloudflare.com/email-service/get-started/route-emails/index.md) | +| Process incoming email in code | Email Workers | Apply custom logic to inbound messages | `cloudflare-email-service` skill; [Email handler](https://developers.cloudflare.com/email-service/api/route-emails/email-handler/index.md) | +| Manage third-party tags and scripts | Zaraz | Load and manage third-party tools through Cloudflare | [Zaraz docs](https://developers.cloudflare.com/zaraz/index.md) | +| Run locally and manage resources from the CLI | Cloudflare CLI (`cf`); Wrangler for existing Wrangler projects | Develop, configure, deploy, and inspect the intended account and environment; choose the CLI as described in [Choose the CLI](#choose-the-cli) | `cf-cli` skill; [cf docs](https://developers.cloudflare.com/cf/index.md); `wrangler` skill for Wrangler projects | +| Test Worker behavior before deployment | Workers testing tools | Choose runtime tests or integration tests for the affected behavior | [Testing docs](https://developers.cloudflare.com/workers/testing/index.md); `durable-objects` skill for DO tests | | Embed local Worker simulation in tooling | Miniflare | A programmatic emulator is needed for a custom development or test harness | [Miniflare](references/miniflare/README.md) | -| Run or investigate the underlying Workers runtime | workerd | Work directly with the runtime outside normal managed deployment | [workerd](references/workerd/README.md) | -| Try a small Worker in the browser | Workers Playground | Explore or share a minimal example without local setup | [Workers Playground](references/workers-playground/README.md) | -| Build and deploy whenever code is pushed | Workers Builds | Connect a Git repository to automated builds and deployments | [Builds docs](https://developers.cloudflare.com/workers/ci-cd/builds/) | -| Preview a version, release it gradually, or roll back code | Workers versions and deployments | Manage application releases; rollback does not restore connected resource data | [Deployment docs](https://developers.cloudflare.com/workers/versions-and-deployments/); `wrangler` skill | -| Release a feature gradually or target user groups | Flagship | Change feature availability with targeting and percentage rollouts | [Flagship](references/flagship/README.md) | -| Manage infrastructure as code | Terraform or Pulumi | Use Terraform for declarative configuration or Pulumi for infrastructure in programming languages | [Terraform](references/terraform/README.md); [Pulumi](references/pulumi/README.md) | -| Automate account or product configuration through an API | Cloudflare REST API | Manage resources programmatically; prefer bindings for supported operations inside Workers | [REST API](references/api/README.md) | -| Debug failures and trace application requests | Workers Logs and Traces | Investigate runtime errors and execution paths | [Observability](references/observability/README.md) | +| Run or investigate the underlying Workers runtime | workerd | Work directly with the runtime outside normal managed deployment | [workerd repository](https://github.com/cloudflare/workerd) | +| Try a small Worker in the browser | Workers Playground | Explore or share a minimal example without local setup | [Workers Playground docs](https://developers.cloudflare.com/workers/playground/index.md) | +| Build and deploy whenever code is pushed | Workers Builds | Connect a Git repository to automated builds and deployments | [Builds docs](https://developers.cloudflare.com/workers/ci-cd/builds/index.md) | +| Test a branch or pull request in an isolated environment | Workers Previews | Create a branch environment under the same Worker with its own settings and URLs; check which bound resources are isolated or shared | [Previews docs](https://developers.cloudflare.com/workers/previews/index.md); `wrangler` skill | +| Inspect an uploaded version, release it gradually, or roll back code | Workers versions and deployments | Manage application releases that use production resources; rollback does not restore connected resource data | [Deployment docs](https://developers.cloudflare.com/workers/versions-and-deployments/index.md); `wrangler` skill | +| Release a feature gradually or target user groups | Flagship | Change feature availability with targeting and percentage rollouts | [Flagship docs](https://developers.cloudflare.com/flagship/index.md) | +| Manage infrastructure as code | Terraform or Pulumi | Use Terraform for declarative configuration or Pulumi for infrastructure in programming languages | [Terraform docs](https://developers.cloudflare.com/terraform/index.md); [Pulumi docs](https://developers.cloudflare.com/pulumi/index.md) | +| Automate account or product configuration through an API | Cloudflare REST API | Manage resources programmatically; prefer bindings for supported operations inside Workers | [REST API docs](https://developers.cloudflare.com/fundamentals/api/index.md) | +| Debug failures and trace application requests | Local Explorer; Workers Logs and Traces | Inspect binding data and traces locally, or investigate deployed runtime errors | `workers-development` skill; [Observability](references/observability/README.md) | +| Profile CPU usage or memory allocations in deployed Workers or Durable Objects | Workers profiling | Investigate CPU and memory issues in production | [workers-profiling skill](../workers-profiling/SKILL.md) | | Process Worker execution events in code | Tail Workers | Build custom log or exception processing | [Tail Workers](references/tail-workers/README.md) | -| Export Worker logs to another system | Workers Logpush | Deliver logs to a supported external destination | [Logpush docs](https://developers.cloudflare.com/workers/observability/logs/logpush/) | -| Measure custom application events | Workers Analytics Engine | Analyze high-cardinality event data written from Workers | [Analytics Engine](references/analytics-engine/README.md) | -| Measure website usage and visitor performance | Cloudflare Web Analytics | Add website analytics and real-user measurements | [Web Analytics](references/web-analytics/README.md) | -| Query metrics across Cloudflare products | GraphQL Analytics API | Retrieve product analytics programmatically | [GraphQL Analytics API](references/graphql-api/README.md) | -| Audit page speed and find loading bottlenecks | Web performance tools | Measure and improve the site's actual browser performance | `web-perf` skill; [Web Analytics](references/web-analytics/README.md) | -| Ask questions about an account or diagnose its configuration in the dashboard | Agent Lee | Use the dashboard's AI assistant; check current account eligibility | [Agent Lee docs](https://developers.cloudflare.com/agent-lee/) | +| Export Worker logs to another system | Workers Logpush | Deliver logs to a supported external destination | [Logpush docs](https://developers.cloudflare.com/workers/observability/logs/logpush/index.md) | +| Measure custom application events | Workers Analytics Engine | Analyze high-cardinality event data written from Workers | [Analytics Engine docs](https://developers.cloudflare.com/analytics/analytics-engine/index.md) | +| Measure website usage and visitor performance | Cloudflare Web Analytics | Add website analytics and real-user measurements | [Web Analytics docs](https://developers.cloudflare.com/web-analytics/index.md) | +| Query metrics across Cloudflare products | GraphQL Analytics API | Retrieve product analytics programmatically | [GraphQL Analytics API docs](https://developers.cloudflare.com/analytics/graphql-api/index.md) | +| Audit page speed and find loading bottlenecks | Web performance tools | Measure and improve the site's actual browser performance | `web-perf` skill; [Web Analytics docs](https://developers.cloudflare.com/web-analytics/index.md) | +| Ask questions about an account or diagnose its configuration in the dashboard | Agent Lee | Use the dashboard's AI assistant; check current account eligibility | [Agent Lee docs](https://developers.cloudflare.com/agent-lee/index.md) | For example, a file-upload app can use Workers for its API, R2 for files, D1 for metadata, and Queues for processing. A document assistant can start with Workers and AI Search; use Vectorize and Workers AI when it needs custom retrieval. Recommend only the pieces the requested behavior needs. ## Find guidance for a task not listed here -Use the [Cloudflare product directory](https://developers.cloudflare.com/directory/) for additional products and their current docs. Follow links to the specific feature or API involved. Use [Choose a data or storage product](https://developers.cloudflare.com/workers/platform/storage-options/) for storage tradeoffs, and the product's limits, pricing, and migration guides when evaluating scale, cost, or an upgrade. This table maps common tasks to selected Cloudflare products; it does not enumerate every possible application. +Use the [Cloudflare product directory](https://developers.cloudflare.com/directory/index.md) for additional products and their current docs. Follow links to the specific feature or API involved. Use [Choose a data or storage product](https://developers.cloudflare.com/workers/platform/storage-options/index.md) for storage tradeoffs, and the product's limits, pricing, and migration guides when evaluating scale, cost, or an upgrade. This table maps common tasks to selected Cloudflare products; it does not enumerate every possible application. ## Caching -Prefer [Workers Cache](https://developers.cloudflare.com/workers/cache/) for caching, including [advanced patterns](https://developers.cloudflare.com/workers/cache/examples/) using cached inner entrypoints and programmatic invalidation. Choose [Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/) or KV caching only when a concrete requirement cannot be met by Workers Cache; check its [patterns](https://developers.cloudflare.com/workers/cache/examples/) and [limitations](https://developers.cloudflare.com/workers/cache/limitations/) first. +Prefer [Workers Cache](https://developers.cloudflare.com/workers/cache/index.md) for caching, including [advanced patterns](https://developers.cloudflare.com/workers/cache/examples/index.md) using cached inner entrypoints and programmatic invalidation. Choose [Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/index.md) or KV caching only when a concrete requirement cannot be met by Workers Cache; check its [patterns](https://developers.cloudflare.com/workers/cache/examples/index.md) and [limitations](https://developers.cloudflare.com/workers/cache/limitations/index.md) first. + +## Troubleshoot HTTP errors on a proxied site + +Work out whether the fix belongs in the zone's Cloudflare configuration or at the origin before suggesting one. Retrieve the page for the specific code from [HTTP status codes](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/index.md); do not answer from memory. [Custom Errors](https://developers.cloudflare.com/rules/custom-errors/index.md) can change how error pages look and which status they return, so ask whether the zone uses them before relying on branding. + +- **Do not judge `502` and `504` by branding.** A Cloudflare-branded `502` or `504` usually means the origin returned that status; an unbranded blank page came from Cloudflare. Refer to [Error 502 or 504](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-502-504/index.md). +- **Read the diagnostic headers for other errors.** A `cf-error-type` response header means Cloudflare generated the page, and its value names the category; a Cloudflare-generated page can still need a fix at the origin. Refer to [Cloudflare error diagnostic headers](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-error-headers/index.md). +- **A 1xxx code means Cloudflare generated the response**, often because of the site's own configuration, such as a security rule, DNS record, Worker, or Tunnel. The code appears in the page body and the HTTP status differs; map them with the [HTTP status codes table](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-1xxx-errors/index.md#http-status-codes), then open that error's page. Cloudflare 10xxx codes are API errors, not proxied-site errors. +- **`520`–`526` are Cloudflare pages for a failed origin connection**: an unexpected response, no connection, a timeout, or a TLS failure. Start at the origin: server logs, firewalls that block [Cloudflare IP ranges](https://www.cloudflare.com/ips/), and any load balancer or proxy in between. For `525` and `526`, also check the zone's SSL/TLS encryption mode. Refer to [Cloudflare 5xx errors](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/index.md). +- **For a `403`, check the page first.** A `403` without Cloudflare branding usually comes from the origin. If the page shows a 1xxx code, open that error's page. For error 1020 or the WAF block page, search Security Events for the Ray ID or client IP to find the rule; WAF custom and managed rules serve the WAF block page, not error 1020. Security Events shows sampled logs, so a missing event does not prove the origin returned the error. Refer to [Error 403](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/4xx-client-error/error-403/index.md), [Error 1020](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-1xxx-errors/error-1020/index.md), and [Security Events](https://developers.cloudflare.com/waf/analytics/security-events/index.md). +- **Only the site owner can fix most of these.** If the user is a visitor, explain the error and send them to the site owner. ## Working principles @@ -133,5 +162,9 @@ Prefer [Workers Cache](https://developers.cloudflare.com/workers/cache/) for cac - Check current Cloudflare docs before relying on limits, prices, compatibility flags, or security requirements; these can change. - Validate in proportion to the change: use the project's checks, then exercise the affected behavior when practical. -Cloudflare documentation: -Cloudflare changelog: +Cloudflare documentation: +Cloudflare changelog: + +## Skill feedback + +If the user reports that this skill gave incorrect or outdated guidance, or is missing information, read [references/skill-feedback.md](references/skill-feedback.md). diff --git a/external/develop/devops/cloudflare/references/ai-gateway/README.md b/external/develop/devops/cloudflare/references/ai-gateway/README.md index 54ac9e9..a4bb1a1 100644 --- a/external/develop/devops/cloudflare/references/ai-gateway/README.md +++ b/external/develop/devops/cloudflare/references/ai-gateway/README.md @@ -14,14 +14,14 @@ Fetch the linked documentation before choosing endpoints, authentication headers | Add fallbacks, conditional routing, or traffic splits | [Dynamic routing](./dynamic-routing.md) | | Diagnose failed requests, caching, or missing logs | [Troubleshooting](./troubleshooting.md) | -For new single-model calls, start with the [REST API](https://developers.cloudflare.com/ai-gateway/usage/rest-api/) or [Workers bindings](https://developers.cloudflare.com/ai-gateway/usage/worker-binding-methods/), depending on the runtime. Preserve provider-native integrations when their API shape is needed; use the corresponding [provider guide](https://developers.cloudflare.com/ai-gateway/usage/providers/). +For new single-model calls, start with the [REST API](https://developers.cloudflare.com/ai-gateway/usage/rest-api/index.md) or [Workers bindings](https://developers.cloudflare.com/ai-gateway/usage/worker-binding-methods/index.md), depending on the runtime. Preserve provider-native integrations when their API shape is needed; use the corresponding [provider guide](https://developers.cloudflare.com/ai-gateway/usage/providers/index.md). -The [legacy Unified API](https://developers.cloudflare.com/ai-gateway/usage/chat-completion/) is deprecated for single-model calls but remains required for dynamic routes. Check the task before changing an existing endpoint. +The [legacy Unified API](https://developers.cloudflare.com/ai-gateway/usage/chat-completion/index.md) is deprecated for single-model calls but remains required for dynamic routes. Check the task before changing an existing endpoint. Gateway authentication and upstream provider credentials are separate concerns. Choose the endpoint first, then follow its authentication and billing requirements in [configuration](./configuration.md). ## Related references - [Workers AI](../workers-ai/README.md) — model inference. -- [Agents SDK documentation](https://developers.cloudflare.com/agents/) — stateful agents. +- [Agents SDK documentation](https://developers.cloudflare.com/agents/index.md) — stateful agents. - [Vectorize](../vectorize/README.md) — vector search. diff --git a/external/develop/devops/cloudflare/references/ai-gateway/configuration.md b/external/develop/devops/cloudflare/references/ai-gateway/configuration.md index b26863a..6483366 100644 --- a/external/develop/devops/cloudflare/references/ai-gateway/configuration.md +++ b/external/develop/devops/cloudflare/references/ai-gateway/configuration.md @@ -4,14 +4,14 @@ Choose the request path before configuring authentication: Cloudflare REST infer | Task | Current documentation | |------|-----------------------| -| Send a first request and locate the account/gateway identifiers | [Getting started](https://developers.cloudflare.com/ai-gateway/get-started/) | -| Create, update, or delete a gateway; use the default gateway | [Manage gateways](https://developers.cloudflare.com/ai-gateway/configuration/manage-gateway/) | -| Manage gateways programmatically | [Gateway management API](https://developers.cloudflare.com/api/resources/ai_gateway/methods/list/) | -| Protect gateway access and choose token permissions for the endpoint | [Authenticated Gateway](https://developers.cloudflare.com/ai-gateway/configuration/authentication/) and [REST API authentication](https://developers.cloudflare.com/ai-gateway/usage/rest-api/#authentication) | -| Configure Wrangler and an AI binding | [Workers AI binding setup](https://developers.cloudflare.com/ai-gateway/integrations/aig-workers-ai-binding/) and [binding methods](https://developers.cloudflare.com/ai-gateway/usage/worker-binding-methods/) | -| Store provider keys, select aliases, or diagnose missing credentials | [Bring Your Own Keys](https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/) | -| Use Cloudflare billing or determine which credentials take precedence | [Unified Billing](https://developers.cloudflare.com/ai-gateway/features/unified-billing/) | -| Supply provider credentials with each request | [Provider guides](https://developers.cloudflare.com/ai-gateway/usage/providers/) | +| Send a first request and locate the account/gateway identifiers | [Getting started](https://developers.cloudflare.com/ai-gateway/get-started/index.md) | +| Create, update, or delete a gateway; use the default gateway | [Manage gateways](https://developers.cloudflare.com/ai-gateway/configuration/manage-gateway/index.md) | +| Manage gateways programmatically | [Gateway management API](https://developers.cloudflare.com/api/resources/ai_gateway/methods/list/index.md) | +| Protect gateway access and choose token permissions for the endpoint | [Authenticated Gateway](https://developers.cloudflare.com/ai-gateway/configuration/authentication/index.md) and [REST API authentication](https://developers.cloudflare.com/ai-gateway/usage/rest-api/index.md#authentication) | +| Configure Wrangler and an AI binding | [Workers AI binding setup](https://developers.cloudflare.com/ai-gateway/integrations/aig-workers-ai-binding/index.md) and [binding methods](https://developers.cloudflare.com/ai-gateway/usage/worker-binding-methods/index.md) | +| Store provider keys, select aliases, or diagnose missing credentials | [Bring Your Own Keys](https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/index.md) | +| Use Cloudflare billing or determine which credentials take precedence | [Unified Billing](https://developers.cloudflare.com/ai-gateway/features/unified-billing/index.md) | +| Supply provider credentials with each request | [Provider guides](https://developers.cloudflare.com/ai-gateway/usage/providers/index.md) | Keep credentials out of source code. Use the chosen endpoint's documentation for headers and permissions instead of reusing an authentication recipe from another endpoint. Check credential precedence before changing an existing BYOK or billing setup. diff --git a/external/develop/devops/cloudflare/references/ai-gateway/dynamic-routing.md b/external/develop/devops/cloudflare/references/ai-gateway/dynamic-routing.md index 5694923..d12212f 100644 --- a/external/develop/devops/cloudflare/references/ai-gateway/dynamic-routing.md +++ b/external/develop/devops/cloudflare/references/ai-gateway/dynamic-routing.md @@ -4,12 +4,12 @@ Use a dynamic route when model selection, traffic splitting, quotas, or fallback | Task | Current documentation | |------|-----------------------| -| Design conditional routes, A/B splits, quotas, model fallbacks, and version rollbacks | [Dynamic routing](https://developers.cloudflare.com/ai-gateway/features/dynamic-routing/) | -| Invoke a route from an SDK, HTTP request, or Worker; inspect route response metadata | [Using a dynamic route](https://developers.cloudflare.com/ai-gateway/features/dynamic-routing/usage/) | -| Define route elements and connections programmatically | [JSON configuration](https://developers.cloudflare.com/ai-gateway/features/dynamic-routing/json-configuration/) | -| Provide metadata used by routing conditions | [Custom metadata](https://developers.cloudflare.com/ai-gateway/observability/custom-metadata/) | -| Configure model/provider fallbacks | [Fallbacks](https://developers.cloudflare.com/ai-gateway/configuration/fallbacks/) | -| Configure retries, backoff, and timeouts | [Request handling](https://developers.cloudflare.com/ai-gateway/configuration/request-handling/) | -| Inspect request outcomes, costs, and errors | [Analytics](https://developers.cloudflare.com/ai-gateway/observability/analytics/) and [logging](https://developers.cloudflare.com/ai-gateway/observability/logging/) | +| Design conditional routes, A/B splits, quotas, model fallbacks, and version rollbacks | [Dynamic routing](https://developers.cloudflare.com/ai-gateway/features/dynamic-routing/index.md) | +| Invoke a route from an SDK, HTTP request, or Worker; inspect route response metadata | [Using a dynamic route](https://developers.cloudflare.com/ai-gateway/features/dynamic-routing/usage/index.md) | +| Define route elements and connections programmatically | [JSON configuration](https://developers.cloudflare.com/ai-gateway/features/dynamic-routing/json-configuration/index.md) | +| Provide metadata used by routing conditions | [Custom metadata](https://developers.cloudflare.com/ai-gateway/observability/custom-metadata/index.md) | +| Configure model/provider fallbacks | [Fallbacks](https://developers.cloudflare.com/ai-gateway/configuration/fallbacks/index.md) | +| Configure retries, backoff, and timeouts | [Request handling](https://developers.cloudflare.com/ai-gateway/configuration/request-handling/index.md) | +| Inspect request outcomes, costs, and errors | [Analytics](https://developers.cloudflare.com/ai-gateway/observability/analytics/index.md) and [logging](https://developers.cloudflare.com/ai-gateway/observability/logging/index.md) | -Check the usage guide's authentication and stored-key prerequisites. Dynamic routes still use the [Unified API compatibility endpoint](https://developers.cloudflare.com/ai-gateway/usage/chat-completion/); its single-model deprecation does not make the REST inference endpoint a replacement for route invocation. +Check the usage guide's authentication and stored-key prerequisites. Dynamic routes still use the [Unified API compatibility endpoint](https://developers.cloudflare.com/ai-gateway/usage/chat-completion/index.md); its single-model deprecation does not make the REST inference endpoint a replacement for route invocation. diff --git a/external/develop/devops/cloudflare/references/ai-gateway/features.md b/external/develop/devops/cloudflare/references/ai-gateway/features.md index fc0e4e4..9dcbaf7 100644 --- a/external/develop/devops/cloudflare/references/ai-gateway/features.md +++ b/external/develop/devops/cloudflare/references/ai-gateway/features.md @@ -4,18 +4,18 @@ Fetch the relevant guide before setting feature flags, headers, limits, or billi | Task | Current documentation | |------|-----------------------| -| Enable caching, set TTLs, bypass the cache, or choose a custom cache key | [Caching](https://developers.cloudflare.com/ai-gateway/features/caching/) | -| Control request volume with fixed or sliding limits | [Rate limiting](https://developers.cloudflare.com/ai-gateway/features/rate-limiting/) | -| Enforce cost budgets | [Spend limits](https://developers.cloudflare.com/ai-gateway/features/spend-limits/) | -| Evaluate and enforce content policies | [Guardrails setup](https://developers.cloudflare.com/ai-gateway/features/guardrails/set-up-guardrail/) and [usage considerations](https://developers.cloudflare.com/ai-gateway/features/guardrails/usage-considerations/) | -| Detect sensitive data in prompts and responses | [DLP setup](https://developers.cloudflare.com/ai-gateway/features/dlp/set-up-dlp/) | -| Choose provider keys or Cloudflare billing | [BYOK](https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/) and [Unified Billing](https://developers.cloudflare.com/ai-gateway/features/unified-billing/) | -| Configure provider data retention for Unified Billing | [Zero Data Retention](https://developers.cloudflare.com/ai-gateway/features/unified-billing/#zero-data-retention-zdr) | -| Configure log collection, payload storage, and retention | [Logging](https://developers.cloudflare.com/ai-gateway/observability/logging/) | -| Export logs | [Workers Logpush](https://developers.cloudflare.com/ai-gateway/observability/logging/logpush/) | -| Attach request metadata for tracking and routing | [Custom metadata](https://developers.cloudflare.com/ai-gateway/observability/custom-metadata/) | -| Override model costs | [Custom costs](https://developers.cloudflare.com/ai-gateway/configuration/custom-costs/) | -| Check supported providers, quotas, or pricing | [Provider guides](https://developers.cloudflare.com/ai-gateway/usage/providers/), [limits](https://developers.cloudflare.com/ai-gateway/reference/limits/), and [pricing](https://developers.cloudflare.com/ai-gateway/reference/pricing/) | +| Enable caching, set TTLs, bypass the cache, or choose a custom cache key | [Caching](https://developers.cloudflare.com/ai-gateway/features/caching/index.md) | +| Control request volume with fixed or sliding limits | [Rate limiting](https://developers.cloudflare.com/ai-gateway/features/rate-limiting/index.md) | +| Enforce cost budgets | [Spend limits](https://developers.cloudflare.com/ai-gateway/features/spend-limits/index.md) | +| Evaluate and enforce content policies | [Guardrails setup](https://developers.cloudflare.com/ai-gateway/features/guardrails/set-up-guardrail/index.md) and [usage considerations](https://developers.cloudflare.com/ai-gateway/features/guardrails/usage-considerations/index.md) | +| Detect sensitive data in prompts and responses | [DLP setup](https://developers.cloudflare.com/ai-gateway/features/dlp/set-up-dlp/index.md) | +| Choose provider keys or Cloudflare billing | [BYOK](https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/index.md) and [Unified Billing](https://developers.cloudflare.com/ai-gateway/features/unified-billing/index.md) | +| Configure provider data retention for Unified Billing | [Zero Data Retention](https://developers.cloudflare.com/ai-gateway/features/unified-billing/index.md#zero-data-retention-zdr) | +| Configure log collection, payload storage, and retention | [Logging](https://developers.cloudflare.com/ai-gateway/observability/logging/index.md) | +| Export logs | [Workers Logpush](https://developers.cloudflare.com/ai-gateway/observability/logging/logpush/index.md) | +| Attach request metadata for tracking and routing | [Custom metadata](https://developers.cloudflare.com/ai-gateway/observability/custom-metadata/index.md) | +| Override model costs | [Custom costs](https://developers.cloudflare.com/ai-gateway/configuration/custom-costs/index.md) | +| Check supported providers, quotas, or pricing | [Provider guides](https://developers.cloudflare.com/ai-gateway/usage/providers/index.md), [limits](https://developers.cloudflare.com/ai-gateway/reference/limits/index.md), and [pricing](https://developers.cloudflare.com/ai-gateway/reference/pricing/index.md) | Choose cache keys only for requests whose responses may safely be shared. Decide what prompt and response data may be stored before enabling logging; do not infer a provider's retention policy from gateway log settings. diff --git a/external/develop/devops/cloudflare/references/ai-gateway/sdk-integration.md b/external/develop/devops/cloudflare/references/ai-gateway/sdk-integration.md index 085a375..a504ed1 100644 --- a/external/develop/devops/cloudflare/references/ai-gateway/sdk-integration.md +++ b/external/develop/devops/cloudflare/references/ai-gateway/sdk-integration.md @@ -4,16 +4,16 @@ Choose an integration that matches the application's runtime and required API sh | Integration task | Current documentation | |------------------|-----------------------| -| New single-model HTTP calls or OpenAI-compatible clients | [REST API](https://developers.cloudflare.com/ai-gateway/usage/rest-api/) | -| Vercel AI SDK, provider adapters, and fallback providers | [Vercel AI SDK integration](https://developers.cloudflare.com/ai-gateway/integrations/vercel-ai-sdk/) | -| Preserve OpenAI-native endpoints with the OpenAI SDK, Python, or HTTP | [OpenAI provider guide](https://developers.cloudflare.com/ai-gateway/usage/providers/openai/) | -| Preserve Anthropic-native requests | [Anthropic provider guide](https://developers.cloudflare.com/ai-gateway/usage/providers/anthropic/) | -| Use another provider or a framework's configurable provider endpoint | [Provider guides](https://developers.cloudflare.com/ai-gateway/usage/providers/) — match the framework's expected API shape to the provider endpoint | -| Configure Workers AI and an AI binding | [Binding setup](https://developers.cloudflare.com/ai-gateway/integrations/aig-workers-ai-binding/) | -| Call Workers AI or third-party models from a Worker; use gateway methods | [Workers binding methods](https://developers.cloudflare.com/ai-gateway/usage/worker-binding-methods/) | -| Set request metadata, caching, or other gateway headers | [Header glossary](https://developers.cloudflare.com/ai-gateway/glossary/) | -| Invoke a dynamic route | [Dynamic route usage](https://developers.cloudflare.com/ai-gateway/features/dynamic-routing/usage/) | +| New single-model HTTP calls or OpenAI-compatible clients | [REST API](https://developers.cloudflare.com/ai-gateway/usage/rest-api/index.md) | +| Vercel AI SDK, provider adapters, and fallback providers | [Vercel AI SDK integration](https://developers.cloudflare.com/ai-gateway/integrations/vercel-ai-sdk/index.md) | +| Preserve OpenAI-native endpoints with the OpenAI SDK, Python, or HTTP | [OpenAI provider guide](https://developers.cloudflare.com/ai-gateway/usage/providers/openai/index.md) | +| Preserve Anthropic-native requests | [Anthropic provider guide](https://developers.cloudflare.com/ai-gateway/usage/providers/anthropic/index.md) | +| Use another provider or a framework's configurable provider endpoint | [Provider guides](https://developers.cloudflare.com/ai-gateway/usage/providers/index.md) — match the framework's expected API shape to the provider endpoint | +| Configure Workers AI and an AI binding | [Binding setup](https://developers.cloudflare.com/ai-gateway/integrations/aig-workers-ai-binding/index.md) | +| Call Workers AI or third-party models from a Worker; use gateway methods | [Workers binding methods](https://developers.cloudflare.com/ai-gateway/usage/worker-binding-methods/index.md) | +| Set request metadata, caching, or other gateway headers | [Header glossary](https://developers.cloudflare.com/ai-gateway/glossary/index.md) | +| Invoke a dynamic route | [Dynamic route usage](https://developers.cloudflare.com/ai-gateway/features/dynamic-routing/usage/index.md) | -The [legacy Unified API](https://developers.cloudflare.com/ai-gateway/usage/chat-completion/) is deprecated for single-model calls, but dynamic routes still require its compatibility endpoint. Do not migrate a dynamic route to the REST inference endpoint as though it were a single-model call. +The [legacy Unified API](https://developers.cloudflare.com/ai-gateway/usage/chat-completion/index.md) is deprecated for single-model calls, but dynamic routes still require its compatibility endpoint. Do not migrate a dynamic route to the REST inference endpoint as though it were a single-model call. Confirm [gateway authentication and provider credentials](./configuration.md) separately, including the selected path's BYOK and billing behavior. diff --git a/external/develop/devops/cloudflare/references/ai-gateway/troubleshooting.md b/external/develop/devops/cloudflare/references/ai-gateway/troubleshooting.md index f330cdb..b1c9ef3 100644 --- a/external/develop/devops/cloudflare/references/ai-gateway/troubleshooting.md +++ b/external/develop/devops/cloudflare/references/ai-gateway/troubleshooting.md @@ -4,13 +4,13 @@ Identify the request path and whether the failure comes from gateway access, ups | Symptom or task | Current documentation | |-----------------|-----------------------| -| Authentication or provider errors, timeouts, DLP failures, or unexpected cache behavior | [Troubleshooting](https://developers.cloudflare.com/ai-gateway/reference/troubleshooting/) | -| Gateway authentication failure | [Authenticated Gateway](https://developers.cloudflare.com/ai-gateway/configuration/authentication/) and [REST API authentication](https://developers.cloudflare.com/ai-gateway/usage/rest-api/#authentication) | -| Provider key or billing mismatch | [BYOK](https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/), [Unified Billing](https://developers.cloudflare.com/ai-gateway/features/unified-billing/), and the [provider guide](https://developers.cloudflare.com/ai-gateway/usage/providers/) | -| Rate limits or repeated failures | [Rate limiting](https://developers.cloudflare.com/ai-gateway/features/rate-limiting/) and [request handling](https://developers.cloudflare.com/ai-gateway/configuration/request-handling/) | -| Unexpected cache hit or miss, including streaming behavior | [Caching](https://developers.cloudflare.com/ai-gateway/features/caching/) | -| Missing logs, collection overrides, or storage limits | [Logging](https://developers.cloudflare.com/ai-gateway/observability/logging/) and [limits](https://developers.cloudflare.com/ai-gateway/reference/limits/) | -| Inspect headers, request metadata, usage, or export logs | [Header glossary](https://developers.cloudflare.com/ai-gateway/glossary/), [custom metadata](https://developers.cloudflare.com/ai-gateway/observability/custom-metadata/), [analytics](https://developers.cloudflare.com/ai-gateway/observability/analytics/), and [Logpush](https://developers.cloudflare.com/ai-gateway/observability/logging/logpush/) | -| Dynamic route failure | [Dynamic route usage](https://developers.cloudflare.com/ai-gateway/features/dynamic-routing/usage/) | +| Authentication or provider errors, timeouts, DLP failures, or unexpected cache behavior | [Troubleshooting](https://developers.cloudflare.com/ai-gateway/reference/troubleshooting/index.md) | +| Gateway authentication failure | [Authenticated Gateway](https://developers.cloudflare.com/ai-gateway/configuration/authentication/index.md) and [REST API authentication](https://developers.cloudflare.com/ai-gateway/usage/rest-api/index.md#authentication) | +| Provider key or billing mismatch | [BYOK](https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/index.md), [Unified Billing](https://developers.cloudflare.com/ai-gateway/features/unified-billing/index.md), and the [provider guide](https://developers.cloudflare.com/ai-gateway/usage/providers/index.md) | +| Rate limits or repeated failures | [Rate limiting](https://developers.cloudflare.com/ai-gateway/features/rate-limiting/index.md) and [request handling](https://developers.cloudflare.com/ai-gateway/configuration/request-handling/index.md) | +| Unexpected cache hit or miss, including streaming behavior | [Caching](https://developers.cloudflare.com/ai-gateway/features/caching/index.md) | +| Missing logs, collection overrides, or storage limits | [Logging](https://developers.cloudflare.com/ai-gateway/observability/logging/index.md) and [limits](https://developers.cloudflare.com/ai-gateway/reference/limits/index.md) | +| Inspect headers, request metadata, usage, or export logs | [Header glossary](https://developers.cloudflare.com/ai-gateway/glossary/index.md), [custom metadata](https://developers.cloudflare.com/ai-gateway/observability/custom-metadata/index.md), [analytics](https://developers.cloudflare.com/ai-gateway/observability/analytics/index.md), and [Logpush](https://developers.cloudflare.com/ai-gateway/observability/logging/logpush/index.md) | +| Dynamic route failure | [Dynamic route usage](https://developers.cloudflare.com/ai-gateway/features/dynamic-routing/usage/index.md) | Check the existing SDK and gateway retry settings together before adding another retry loop. Follow [SDK integration](./sdk-integration.md) when an endpoint or model format is suspect. diff --git a/external/develop/devops/cloudflare/references/ai-search/README.md b/external/develop/devops/cloudflare/references/ai-search/README.md index 1523517..c776b40 100644 --- a/external/develop/devops/cloudflare/references/ai-search/README.md +++ b/external/develop/devops/cloudflare/references/ai-search/README.md @@ -1,6 +1,6 @@ # Cloudflare AI Search -Use AI Search for managed content indexing and retrieval, with optional answer generation. Start with [How AI Search works](https://developers.cloudflare.com/ai-search/concepts/how-ai-search-works/). +Use AI Search for managed content indexing and retrieval, with optional answer generation. Start with [How AI Search works](https://developers.cloudflare.com/ai-search/concepts/how-ai-search-works/index.md). ## Choose the right product @@ -8,7 +8,7 @@ Use AI Search for managed content indexing and retrieval, with optional answer g - **Custom embeddings and vector-index management:** [Vectorize](../vectorize/README.md). - **Model inference without a managed retrieval pipeline:** [Workers AI](../workers-ai/README.md). -For freshness requirements, read [Syncing](https://developers.cloudflare.com/ai-search/configuration/indexing/syncing/) for your data source before choosing an architecture. Use the current [limits and pricing](https://developers.cloudflare.com/ai-search/platform/limits-pricing/) instead of assuming a fixed indexing interval or account limit. +For freshness requirements, read [Syncing](https://developers.cloudflare.com/ai-search/configuration/indexing/syncing/index.md) for your data source before choosing an architecture. Use the current [limits and pricing](https://developers.cloudflare.com/ai-search/platform/limits-pricing/index.md) instead of assuming a fixed indexing interval or account limit. ## Find the right documentation @@ -16,10 +16,10 @@ Read the linked page before implementing; these references route to the maintain | Task | Start here | |------|------------| -| Build a new Worker integration | [Workers binding quick start](https://developers.cloudflare.com/ai-search/get-started/workers/) | +| Build a new Worker integration | [Workers binding quick start](https://developers.cloudflare.com/ai-search/get-started/workers/index.md) | | Choose an API or maintain an existing integration | [API routes](api.md) | | Connect data, configure indexing, or manage environments | [Configuration routes](configuration.md) | | Choose retrieval, generation, or tenant isolation patterns | [Pattern routes](patterns.md) | | Diagnose indexing, authentication, filters, or limits | [Troubleshooting routes](gotchas.md) | -Existing `env.AI.autorag()` integrations can continue to work. Use the [migration guide](https://developers.cloudflare.com/ai-search/api/migration/workers-binding/) when upgrading; migration is not required just to maintain an existing integration. +Existing `env.AI.autorag()` integrations can continue to work. Use the [migration guide](https://developers.cloudflare.com/ai-search/api/migration/workers-binding/index.md) when upgrading; migration is not required just to maintain an existing integration. diff --git a/external/develop/devops/cloudflare/references/ai-search/api.md b/external/develop/devops/cloudflare/references/ai-search/api.md index b439258..a42a8a2 100644 --- a/external/develop/devops/cloudflare/references/ai-search/api.md +++ b/external/develop/devops/cloudflare/references/ai-search/api.md @@ -4,10 +4,10 @@ Choose documentation that matches the integration you are working on. Fetch the | Task | Documentation | |------|---------------| -| New Worker integration: bindings, search, chat completions, and streaming | [Search Workers binding](https://developers.cloudflare.com/ai-search/api/search/workers-binding/) | -| Create, list, configure, and inspect instances | [Instances Workers binding](https://developers.cloudflare.com/ai-search/api/instances/workers-binding/) | -| Query over HTTP and configure request authentication | [Search REST API](https://developers.cloudflare.com/ai-search/api/search/rest-api/) | -| Maintain an existing `env.AI.autorag()` integration | [Legacy Workers binding](https://developers.cloudflare.com/ai-search/api/migration/workers-binding-legacy/) | -| Upgrade a legacy binding, including responses, streaming, and filters | [Workers binding migration](https://developers.cloudflare.com/ai-search/api/migration/workers-binding/) | +| New Worker integration: bindings, search, chat completions, and streaming | [Search Workers binding](https://developers.cloudflare.com/ai-search/api/search/workers-binding/index.md) | +| Create, list, configure, and inspect instances | [Instances Workers binding](https://developers.cloudflare.com/ai-search/api/instances/workers-binding/index.md) | +| Query over HTTP and configure request authentication | [Search REST API](https://developers.cloudflare.com/ai-search/api/search/rest-api/index.md) | +| Maintain an existing `env.AI.autorag()` integration | [Legacy Workers binding](https://developers.cloudflare.com/ai-search/api/migration/workers-binding-legacy/index.md) | +| Upgrade a legacy binding, including responses, streaming, and filters | [Workers binding migration](https://developers.cloudflare.com/ai-search/api/migration/workers-binding/index.md) | The legacy binding remains supported; use current bindings for new integrations. Keep legacy request and response handling together until deliberately migrating them. diff --git a/external/develop/devops/cloudflare/references/ai-search/configuration.md b/external/develop/devops/cloudflare/references/ai-search/configuration.md index f6d9885..307097a 100644 --- a/external/develop/devops/cloudflare/references/ai-search/configuration.md +++ b/external/develop/devops/cloudflare/references/ai-search/configuration.md @@ -2,15 +2,15 @@ | Task | Documentation | |------|---------------| -| Set up a Worker and local development | [Workers binding quick start](https://developers.cloudflare.com/ai-search/get-started/workers/) | -| Select built-in uploads, R2, or a website; check supported formats | [Data sources](https://developers.cloudflare.com/ai-search/configuration/data-source/) | -| Connect existing R2 content | [R2 data source](https://developers.cloudflare.com/ai-search/configuration/data-source/r2/) | -| Configure crawling and diagnose bot-protection requirements | [Website data source](https://developers.cloudflare.com/ai-search/configuration/data-source/website/) | -| Include or exclude files and URL paths | [Path filtering](https://developers.cloudflare.com/ai-search/configuration/indexing/path-filtering/) | -| Configure source syncs, trigger indexing, or pause and resume | [Syncing](https://developers.cloudflare.com/ai-search/configuration/indexing/syncing/) | -| Grant AI Search access to R2 for indexing | [Service API token](https://developers.cloudflare.com/ai-search/configuration/indexing/service-api-token/) | -| Organize instances by application, tenant, or environment | [Namespaces](https://developers.cloudflare.com/ai-search/concepts/namespaces/) | -| Configure models | [Models](https://developers.cloudflare.com/ai-search/configuration/models/) | -| Inspect instance configuration and indexing progress | [Instances Workers binding](https://developers.cloudflare.com/ai-search/api/instances/workers-binding/) | +| Set up a Worker and local development | [Workers binding quick start](https://developers.cloudflare.com/ai-search/get-started/workers/index.md) | +| Select built-in uploads, R2, or a website; check supported formats | [Data sources](https://developers.cloudflare.com/ai-search/configuration/data-source/index.md) | +| Connect existing R2 content | [R2 data source](https://developers.cloudflare.com/ai-search/configuration/data-source/r2/index.md) | +| Configure crawling and diagnose bot-protection requirements | [Website data source](https://developers.cloudflare.com/ai-search/configuration/data-source/website/index.md) | +| Include or exclude files and URL paths | [Path filtering](https://developers.cloudflare.com/ai-search/configuration/indexing/path-filtering/index.md) | +| Configure source syncs, trigger indexing, or pause and resume | [Syncing](https://developers.cloudflare.com/ai-search/configuration/indexing/syncing/index.md) | +| Grant AI Search access to R2 for indexing | [Service API token](https://developers.cloudflare.com/ai-search/configuration/indexing/service-api-token/index.md) | +| Organize instances by application, tenant, or environment | [Namespaces](https://developers.cloudflare.com/ai-search/concepts/namespaces/index.md) | +| Configure models | [Models](https://developers.cloudflare.com/ai-search/configuration/models/index.md) | +| Inspect instance configuration and indexing progress | [Instances Workers binding](https://developers.cloudflare.com/ai-search/api/instances/workers-binding/index.md) | -Indexing credentials and request authentication serve different purposes. For authenticating search requests over HTTP, use the [Search REST API](https://developers.cloudflare.com/ai-search/api/search/rest-api/) documentation. +Indexing credentials and request authentication serve different purposes. For authenticating search requests over HTTP, use the [Search REST API](https://developers.cloudflare.com/ai-search/api/search/rest-api/index.md) documentation. diff --git a/external/develop/devops/cloudflare/references/ai-search/gotchas.md b/external/develop/devops/cloudflare/references/ai-search/gotchas.md index 7201e3f..8273611 100644 --- a/external/develop/devops/cloudflare/references/ai-search/gotchas.md +++ b/external/develop/devops/cloudflare/references/ai-search/gotchas.md @@ -2,12 +2,12 @@ | Symptom or question | Documentation | |---------------------|---------------| -| API request fails, including authentication or missing instances | [API error codes](https://developers.cloudflare.com/ai-search/troubleshooting/api-error-codes/) | -| Upload or sync succeeds but content fails during processing | [Indexing error codes](https://developers.cloudflare.com/ai-search/troubleshooting/indexing-error-codes/) | -| Content is missing or stale | [Syncing](https://developers.cloudflare.com/ai-search/configuration/indexing/syncing/) and [supported data sources and formats](https://developers.cloudflare.com/ai-search/configuration/data-source/) | -| Filters return unexpected documents or no matches | [Filtering](https://developers.cloudflare.com/ai-search/configuration/retrieval/filtering/) and [metadata attributes](https://developers.cloudflare.com/ai-search/configuration/indexing/metadata/) | -| Thresholds exclude results or responses need tuning | [Result controls](https://developers.cloudflare.com/ai-search/configuration/retrieval/result-controls/) | -| Binding types, response parsing, or streaming fail after an upgrade | [Workers binding migration](https://developers.cloudflare.com/ai-search/api/migration/workers-binding/) | -| Capacity, file-size, or billing questions | [Limits and pricing](https://developers.cloudflare.com/ai-search/platform/limits-pricing/) | +| API request fails, including authentication or missing instances | [API error codes](https://developers.cloudflare.com/ai-search/troubleshooting/api-error-codes/index.md) | +| Upload or sync succeeds but content fails during processing | [Indexing error codes](https://developers.cloudflare.com/ai-search/troubleshooting/indexing-error-codes/index.md) | +| Content is missing or stale | [Syncing](https://developers.cloudflare.com/ai-search/configuration/indexing/syncing/index.md) and [supported data sources and formats](https://developers.cloudflare.com/ai-search/configuration/data-source/index.md) | +| Filters return unexpected documents or no matches | [Filtering](https://developers.cloudflare.com/ai-search/configuration/retrieval/filtering/index.md) and [metadata attributes](https://developers.cloudflare.com/ai-search/configuration/indexing/metadata/index.md) | +| Thresholds exclude results or responses need tuning | [Result controls](https://developers.cloudflare.com/ai-search/configuration/retrieval/result-controls/index.md) | +| Binding types, response parsing, or streaming fail after an upgrade | [Workers binding migration](https://developers.cloudflare.com/ai-search/api/migration/workers-binding/index.md) | +| Capacity, file-size, or billing questions | [Limits and pricing](https://developers.cloudflare.com/ai-search/platform/limits-pricing/index.md) | For legacy binding behavior, start with [API routes](api.md). Do not apply current filter syntax or response shapes to legacy calls without following the migration guide. diff --git a/external/develop/devops/cloudflare/references/ai-search/patterns.md b/external/develop/devops/cloudflare/references/ai-search/patterns.md index 79ec376..a81ee54 100644 --- a/external/develop/devops/cloudflare/references/ai-search/patterns.md +++ b/external/develop/devops/cloudflare/references/ai-search/patterns.md @@ -1,15 +1,15 @@ # AI Search Pattern Routes -Choose retrieval-only search when your application displays chunks or handles generation itself; choose chat completions when AI Search should also generate the answer. Read [Search Workers binding](https://developers.cloudflare.com/ai-search/api/search/workers-binding/) for both paths and streaming behavior. +Choose retrieval-only search when your application displays chunks or handles generation itself; choose chat completions when AI Search should also generate the answer. Read [Search Workers binding](https://developers.cloudflare.com/ai-search/api/search/workers-binding/index.md) for both paths and streaming behavior. | Task | Documentation | |------|---------------| -| Isolate tenants using separate instances or a shared filtered instance | [Multitenancy](https://developers.cloudflare.com/ai-search/how-to/per-tenant-search/) | -| Define built-in or custom metadata | [Metadata attributes](https://developers.cloudflare.com/ai-search/configuration/indexing/metadata/) | -| Filter by metadata, combine conditions, or match a folder and subfolders | [Filtering](https://developers.cloudflare.com/ai-search/configuration/retrieval/filtering/) | -| Tune result count and relevance thresholds | [Result controls](https://developers.cloudflare.com/ai-search/configuration/retrieval/result-controls/) | -| Resolve follow-up queries using conversation context | [Query rewriting](https://developers.cloudflare.com/ai-search/configuration/retrieval/query-rewriting/) | -| Improve result ordering with a second model | [Reranking](https://developers.cloudflare.com/ai-search/configuration/retrieval/reranking/) | -| Customize generation and query-rewriting instructions | [System prompt](https://developers.cloudflare.com/ai-search/configuration/retrieval/system-prompt/) | +| Isolate tenants using separate instances or a shared filtered instance | [Multitenancy](https://developers.cloudflare.com/ai-search/how-to/per-tenant-search/index.md) | +| Define built-in or custom metadata | [Metadata attributes](https://developers.cloudflare.com/ai-search/configuration/indexing/metadata/index.md) | +| Filter by metadata, combine conditions, or match a folder and subfolders | [Filtering](https://developers.cloudflare.com/ai-search/configuration/retrieval/filtering/index.md) | +| Tune result count and relevance thresholds | [Result controls](https://developers.cloudflare.com/ai-search/configuration/retrieval/result-controls/index.md) | +| Resolve follow-up queries using conversation context | [Query rewriting](https://developers.cloudflare.com/ai-search/configuration/retrieval/query-rewriting/index.md) | +| Improve result ordering with a second model | [Reranking](https://developers.cloudflare.com/ai-search/configuration/retrieval/reranking/index.md) | +| Customize generation and query-rewriting instructions | [System prompt](https://developers.cloudflare.com/ai-search/configuration/retrieval/system-prompt/index.md) | For tenant isolation, read the full multitenancy guide before choosing an approach. A lower-bound folder comparison alone does not establish a tenant boundary; use the documented filtering semantics. diff --git a/external/develop/devops/cloudflare/references/analytics-engine/README.md b/external/develop/devops/cloudflare/references/analytics-engine/README.md deleted file mode 100644 index a227c1e..0000000 --- a/external/develop/devops/cloudflare/references/analytics-engine/README.md +++ /dev/null @@ -1,94 +0,0 @@ -# Cloudflare Workers Analytics Engine Reference - -Expert guidance for implementing unlimited-cardinality analytics at scale using Cloudflare Workers Analytics Engine. - -## What is Analytics Engine? - -Time-series analytics database designed for high-cardinality data (millions of unique dimensions). Write data points from Workers, query via SQL API. Use for: -- Custom user-facing analytics dashboards -- Usage-based billing & metering -- Per-customer/per-feature monitoring -- High-frequency instrumentation without performance impact - -**Key Capability:** Track metrics with unlimited unique values (e.g., millions of user IDs, API keys) without performance degradation. - -## Core Concepts - -| Concept | Description | Example | -|---------|-------------|---------| -| **Dataset** | Logical table for related metrics | `api_requests`, `user_events` | -| **Data Point** | Single measurement with timestamp | One API request's metrics | -| **Blobs** | String dimensions (max 20) | endpoint, method, status, user_id | -| **Doubles** | Numeric values (max 20) | latency_ms, request_count, bytes | -| **Indexes** | Filtered blobs for efficient queries | customer_id, api_key | - -## Reading Order - -| Task | Start Here | Then Read | -|------|------------|-----------| -| **First-time setup** | [configuration.md](configuration.md) → [api.md](api.md) → [patterns.md](patterns.md) | | -| **Writing data** | [api.md](api.md) → [gotchas.md](gotchas.md) (sampling) | | -| **Querying data** | [api.md](api.md) (SQL API) → [patterns.md](patterns.md) (examples) | | -| **Debugging** | [gotchas.md](gotchas.md) → [api.md](api.md) (limits) | | -| **Optimization** | [patterns.md](patterns.md) (anti-patterns) → [gotchas.md](gotchas.md) | | - -## When to Use Analytics Engine - -``` -Need to track metrics? → Yes - ↓ -Millions of unique dimension values? → Yes - ↓ - Need real-time queries? → Yes - ↓ - Use Analytics Engine ✓ - -Alternative scenarios: -- Low cardinality (<10k unique values) → Workers Analytics (free tier) -- Complex joins/relations → D1 Database -- Logs/debugging → Tail Workers (logpush) -- External tools → Send to external analytics (Datadog, etc.) -``` - -## Quick Start - -1. Add binding to `wrangler.jsonc`: -```jsonc -{ - "analytics_engine_datasets": [ - { "binding": "ANALYTICS", "dataset": "my_events" } - ] -} -``` - -2. Write data points (fire-and-forget, no await): -```typescript -env.ANALYTICS.writeDataPoint({ - blobs: ["/api/users", "GET", "200"], - doubles: [145.2, 1], // latency_ms, count - indexes: [customerId] -}); -``` - -3. Query via SQL API (HTTP): -```sql -SELECT blob1, SUM(double2) AS total_requests -FROM my_events -WHERE index1 = 'customer_123' - AND timestamp >= NOW() - INTERVAL '7' DAY -GROUP BY blob1 -ORDER BY total_requests DESC -``` - -## In This Reference - -- **[configuration.md](configuration.md)** - Setup, bindings, TypeScript types, limits -- **[api.md](api.md)** - `writeDataPoint()`, SQL API, query syntax -- **[patterns.md](patterns.md)** - Use cases, examples, anti-patterns -- **[gotchas.md](gotchas.md)** - Sampling, index selection, troubleshooting - -## See Also - -- [Cloudflare Analytics Engine Docs](https://developers.cloudflare.com/analytics/analytics-engine/) -- [GraphQL Analytics API Reference](../graphql-api/) - Query built-in Cloudflare analytics (HTTP, Workers, DNS, Firewall, etc.) -- [Observability Reference](../observability/) - Workers Logs, Traces, and real-time debugging diff --git a/external/develop/devops/cloudflare/references/analytics-engine/api.md b/external/develop/devops/cloudflare/references/analytics-engine/api.md deleted file mode 100644 index 8b517ff..0000000 --- a/external/develop/devops/cloudflare/references/analytics-engine/api.md +++ /dev/null @@ -1,112 +0,0 @@ -# Analytics Engine API Reference - -## Writing Data - -### `writeDataPoint()` - -Fire-and-forget (returns `void`, not Promise). Writes happen asynchronously. - -```typescript -interface AnalyticsEngineDataPoint { - blobs?: string[]; // Up to 20 strings (dimensions), 16KB each - doubles?: number[]; // Up to 20 numbers (metrics) - indexes?: string[]; // 1 indexed string for high-cardinality filtering -} - -env.ANALYTICS.writeDataPoint({ - blobs: ["/api/users", "GET", "200"], - doubles: [145.2, 1], // latency_ms, count - indexes: ["customer_abc123"] -}); -``` - -**Behaviors:** No await needed, no error thrown (check tail logs), auto-sampled at high volumes, auto-timestamped. - -**Blob vs Index:** Blob for GROUP BY (<100k unique), Index for filter-only (millions unique). - -### Full Example - -```typescript -export default { - async fetch(request: Request, env: Env): Promise { - const start = Date.now(); - const url = new URL(request.url); - try { - const response = await handleRequest(request); - env.ANALYTICS.writeDataPoint({ - blobs: [url.pathname, request.method, response.status.toString()], - doubles: [Date.now() - start, 1], - indexes: [request.headers.get("x-api-key") || "anonymous"] - }); - return response; - } catch (error) { - env.ANALYTICS.writeDataPoint({ - blobs: [url.pathname, request.method, "500"], - doubles: [Date.now() - start, 1, 0], - }); - throw error; - } - } -}; -``` - -## SQL API (External Only) - -```bash -curl -X POST https://api.cloudflare.com/client/v4/accounts/{account_id}/analytics_engine/sql \ - -H "Authorization: Bearer $TOKEN" \ - -d "SELECT blob1 AS endpoint, COUNT(*) AS requests FROM dataset WHERE timestamp >= NOW() - INTERVAL '1' HOUR GROUP BY blob1" -``` - -### Column References - -```sql --- blob1..blob20, double1..double20, index1, timestamp -SELECT blob1 AS endpoint, SUM(double1) AS latency, COUNT(*) AS requests -FROM my_dataset -WHERE index1 = 'customer_123' AND timestamp >= NOW() - INTERVAL '7' DAY -GROUP BY blob1 -HAVING COUNT(*) > 100 -ORDER BY requests DESC LIMIT 100 -``` - -**Aggregations:** `SUM()`, `AVG()`, `COUNT()`, `MIN()`, `MAX()`, `quantile(0.95)()` - -**Time ranges:** `NOW() - INTERVAL '1' HOUR`, `BETWEEN '2026-01-01' AND '2026-01-31'` - -### Query Examples - -```sql --- Top endpoints -SELECT blob1, COUNT(*) AS requests, AVG(double1) AS avg_latency -FROM api_requests WHERE timestamp >= NOW() - INTERVAL '24' HOUR -GROUP BY blob1 ORDER BY requests DESC LIMIT 20 - --- Error rate -SELECT blob1, COUNT(*) AS total, - SUM(if(blob3 LIKE '5%', 1, 0)) AS errors -FROM api_requests WHERE timestamp >= NOW() - INTERVAL '1' HOUR -GROUP BY blob1 HAVING total > 50 - --- P95 latency -SELECT blob1, quantile(0.95)(double1) AS p95 -FROM api_requests GROUP BY blob1 -``` - -## Response Format - -```json -{"data": [{"endpoint": "/api/users", "requests": 1523}], "rows": 2} -``` - -## Limits - -| Resource | Limit | -|----------|-------| -| Blobs/Doubles per point | 20 each | -| Indexes per point | 1 | -| Blob/Index size | 16KB | -| Data retention | 90 days | -| Query timeout | 30s | - -**Critical:** High write volumes (>1M/min) trigger automatic sampling. diff --git a/external/develop/devops/cloudflare/references/analytics-engine/configuration.md b/external/develop/devops/cloudflare/references/analytics-engine/configuration.md deleted file mode 100644 index a71af96..0000000 --- a/external/develop/devops/cloudflare/references/analytics-engine/configuration.md +++ /dev/null @@ -1,112 +0,0 @@ -# Analytics Engine Configuration - -## Setup - -1. Add binding to `wrangler.jsonc` -2. Deploy Worker -3. Dataset created automatically on first write -4. Query via SQL API - -## wrangler.jsonc - -```jsonc -{ - "name": "my-worker", - "analytics_engine_datasets": [ - { "binding": "ANALYTICS", "dataset": "my_events" } - ] -} -``` - -Multiple datasets for separate concerns: -```jsonc -{ - "analytics_engine_datasets": [ - { "binding": "API_ANALYTICS", "dataset": "api_requests" }, - { "binding": "USER_EVENTS", "dataset": "user_activity" } - ] -} -``` - -## TypeScript - -```typescript -interface Env { - ANALYTICS: AnalyticsEngineDataset; -} - -export default { - async fetch(request: Request, env: Env) { - // No await - returns void, fire-and-forget - env.ANALYTICS.writeDataPoint({ - blobs: [pathname, method, status], // String dimensions (max 20) - doubles: [latency, 1], // Numeric metrics (max 20) - indexes: [apiKey] // High-cardinality filter (max 1) - }); - return response; - } -}; -``` - -## Data Point Limits - -| Field | Limit | SQL Access | -|-------|-------|------------| -| blobs | 20 strings, 16KB each | `blob1`...`blob20` | -| doubles | 20 numbers | `double1`...`double20` | -| indexes | 1 string, 16KB | `index1` | - -## Write Behavior - -| Scenario | Behavior | -|----------|----------| -| <1M writes/min | All accepted | -| >1M writes/min | Automatic sampling | -| Invalid data | Silent failure (check tail logs) | - -**Mitigate sampling:** Pre-aggregate, use multiple datasets, write only critical metrics. - -## Query Limits - -| Resource | Limit | -|----------|-------| -| Query timeout | 30 seconds | -| Data retention | 90 days (default) | -| Result size | ~10MB | - -## Cost - -**Free tier:** 10M writes/month, 1M reads/month - -**Paid:** $0.05 per 1M writes, $1.00 per 1M reads - -## Environment-Specific - -```jsonc -{ - "analytics_engine_datasets": [ - { "binding": "ANALYTICS", "dataset": "prod_events" } - ], - "env": { - "staging": { - "analytics_engine_datasets": [ - { "binding": "ANALYTICS", "dataset": "staging_events" } - ] - } - } -} -``` - -## Monitoring - -```bash -npx wrangler tail # Check for sampling/write errors -``` - -```sql --- Check write activity -SELECT DATE_TRUNC('hour', timestamp) AS hour, COUNT(*) AS writes -FROM my_dataset -WHERE timestamp >= NOW() - INTERVAL '24' HOUR -GROUP BY hour -``` diff --git a/external/develop/devops/cloudflare/references/analytics-engine/gotchas.md b/external/develop/devops/cloudflare/references/analytics-engine/gotchas.md deleted file mode 100644 index ed1767f..0000000 --- a/external/develop/devops/cloudflare/references/analytics-engine/gotchas.md +++ /dev/null @@ -1,85 +0,0 @@ -# Analytics Engine Gotchas - -## Critical Issues - -### Sampling at High Volumes - -**Problem:** Queries return fewer points than written at >1M writes/min. - -**Solution:** -```typescript -// Pre-aggregate before writing -let buffer = { count: 0, total: 0 }; -buffer.count++; buffer.total += value; - -// Write once per second instead of per request -if (Date.now() % 1000 === 0) { - env.ANALYTICS.writeDataPoint({ doubles: [buffer.count, buffer.total] }); -} -``` - -**Detection:** `npx wrangler tail` → look for "sampling enabled" - -### writeDataPoint Returns void - -```typescript -// ❌ Pointless await -await env.ANALYTICS.writeDataPoint({...}); - -// ✅ Fire-and-forget -env.ANALYTICS.writeDataPoint({...}); -``` - -Writes can fail silently. Check tail logs. - -### Index vs Blob - -| Cardinality | Use | Example | -|-------------|-----|---------| -| Millions | **Index** | user_id, api_key | -| Hundreds | **Blob** | endpoint, status_code, country | - -```typescript -// ✅ Correct -{ blobs: [method, path, status], indexes: [userId] } -``` - -### Can't Query from Workers - -Query API requires HTTP auth. Use external service or cache in KV/D1. - -### No Custom Timestamps - -Auto-generated at write time. Store original in blob if needed. - -## Common Errors - -| Error | Fix | -|-------|-----| -| Binding not found | Check wrangler.jsonc, redeploy | -| No data in query | Wait 30s; check dataset name; check time range | -| Query timeout | Add time filter; use index for filtering | - -## Limits - -| Resource | Limit | -|----------|-------| -| Blobs per point | 20 | -| Doubles per point | 20 | -| Indexes per point | 1 | -| Blob/Index size | 16KB | -| Write rate (no sampling) | ~1M/min | -| Retention | 90 days | -| Query timeout | 30s | - -## Best Practices - -✅ Pre-aggregate at high volumes -✅ Use index for high-cardinality (millions) -✅ Always include time filter in queries -✅ Design schema before coding - -❌ Don't await writeDataPoint -❌ Don't use index for low-cardinality -❌ Don't query without time range -❌ Don't assume all writes succeed diff --git a/external/develop/devops/cloudflare/references/analytics-engine/patterns.md b/external/develop/devops/cloudflare/references/analytics-engine/patterns.md deleted file mode 100644 index dc3fc5d..0000000 --- a/external/develop/devops/cloudflare/references/analytics-engine/patterns.md +++ /dev/null @@ -1,83 +0,0 @@ -# Analytics Engine Patterns - -## Use Cases - -| Use Case | Key Metrics | Index On | -|----------|-------------|----------| -| API Metering | requests, bytes, compute_units | api_key | -| Feature Usage | feature, action, duration | user_id | -| Error Tracking | error_type, endpoint, count | customer_id | -| Performance | latency_ms, cache_status | endpoint | -| A/B Testing | variant, conversions | user_id | - -## API Metering (Billing) - -```typescript -env.ANALYTICS.writeDataPoint({ - blobs: [pathname, method, status, tier], - doubles: [1, computeUnits, bytes, latencyMs], - indexes: [apiKey] -}); - -// Query: Monthly usage by customer -// SELECT index1 AS api_key, SUM(double2) AS compute_units -// FROM usage WHERE timestamp >= DATE_TRUNC('month', NOW()) GROUP BY index1 -``` - -## Error Tracking - -```typescript -env.ANALYTICS.writeDataPoint({ - blobs: [endpoint, method, errorName, errorMessage.slice(0, 1000)], - doubles: [1, timeToErrorMs], - indexes: [customerId] -}); -``` - -## Performance Monitoring - -```typescript -env.ANALYTICS.writeDataPoint({ - blobs: [pathname, method, cacheStatus, status], - doubles: [latencyMs, 1], - indexes: [userId] -}); - -// Query: P95 latency by endpoint -// SELECT blob1, quantile(0.95)(double1) AS p95_ms FROM perf GROUP BY blob1 -``` - -## Anti-Patterns - -| ❌ Wrong | ✅ Correct | -|----------|-----------| -| `await writeDataPoint()` | `writeDataPoint()` (fire-and-forget) | -| `indexes: [method]` (low cardinality) | `blobs: [method]`, `indexes: [userId]` | -| `blobs: [JSON.stringify(obj)]` | Store ID in blob, full object in D1/KV | -| Write every request at 10M/min | Pre-aggregate per second | -| Query from Worker | Query from external service/API | - -## Best Practices - -1. **Design schema upfront** - Document blob/double/index assignments -2. **Always include count metric** - `doubles: [latency, 1]` for AVG calculations -3. **Use enums for blobs** - Consistent values like `Status.SUCCESS` -4. **Handle sampling** - Use ratios (avg_latency = SUM(latency)/SUM(count)) -5. **Test queries early** - Validate schema before heavy writes - -## Schema Template - -```typescript -/** - * Dataset: my_metrics - * - * Blobs: - * blob1: endpoint, blob2: method, blob3: status - * - * Doubles: - * double1: latency_ms, double2: count (always 1) - * - * Indexes: - * index1: customer_id (high cardinality) - */ -``` diff --git a/external/develop/devops/cloudflare/references/api-shield/README.md b/external/develop/devops/cloudflare/references/api-shield/README.md deleted file mode 100644 index 86613c8..0000000 --- a/external/develop/devops/cloudflare/references/api-shield/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Cloudflare API Shield Reference - -Expert guidance for API Shield - comprehensive API security suite for discovery, protection, and monitoring. - -## Reading Order - -| Task | Files to Read | -|------|---------------| -| Initial setup | README → configuration.md | -| Implement JWT validation | configuration.md → api.md | -| Add schema validation | configuration.md → patterns.md | -| Detect API attacks | patterns.md → api.md | -| Debug issues | gotchas.md | - -## Feature Selection - -What protection do you need? - -``` -├─ Validate request/response structure → Schema Validation 2.0 (configuration.md) -├─ Verify auth tokens → JWT Validation (configuration.md) -├─ Client certificates → mTLS (configuration.md) -├─ Detect BOLA attacks → BOLA Detection (patterns.md) -├─ Track auth coverage → Auth Posture (patterns.md) -├─ Stop volumetric abuse → Abuse Detection (patterns.md) -└─ Discover shadow APIs → API Discovery (api.md) -``` - -## In This Reference - -- **[configuration.md](configuration.md)** - Setup, session identifiers, rules, token/mTLS configs -- **[api.md](api.md)** - Endpoint management, discovery, validation APIs, GraphQL operations -- **[patterns.md](patterns.md)** - Common patterns, progressive rollout, OWASP mappings, workflows -- **[gotchas.md](gotchas.md)** - Troubleshooting, false positives, performance, best practices - -## Quick Start - -API Shield: Enterprise-grade API security (Discovery, Schema Validation 2.0, JWT, mTLS, BOLA Detection, Auth Posture). Available as Enterprise add-on with preview access. - -## See Also - -- [API Shield Docs](https://developers.cloudflare.com/api-shield/) -- [API Reference](https://developers.cloudflare.com/api/resources/api_gateway/) -- [OWASP API Security Top 10](https://owasp.org/www-project-api-security/) diff --git a/external/develop/devops/cloudflare/references/api-shield/api.md b/external/develop/devops/cloudflare/references/api-shield/api.md deleted file mode 100644 index c833501..0000000 --- a/external/develop/devops/cloudflare/references/api-shield/api.md +++ /dev/null @@ -1,141 +0,0 @@ -# API Reference - -Base: `/zones/{zone_id}/api_gateway` - -## Endpoints - -```bash -GET /operations # List -GET /operations/{op_id} # Get single -POST /operations/item # Create: {endpoint,host,method} -POST /operations # Bulk: {operations:[{endpoint,host,method}]} -DELETE /operations/{op_id} # Delete -DELETE /operations # Bulk delete: {operation_ids:[...]} -``` - -## Discovery - -```bash -GET /discovery/operations # List discovered -PATCH /discovery/operations/{op_id} # Update: {state:"saved"|"ignored"} -PATCH /discovery/operations # Bulk: {operation_ids:{id:{state}}} -GET /discovery # OpenAPI export -``` - -## Config - -```bash -GET /configuration # Get session ID config -PUT /configuration # Update: {auth_id_characteristics:[{name,type:"header"|"cookie"}]} -``` - -## Token Validation - -```bash -GET /token_validation # List -POST /token_validation # Create: {name,location:{header:"..."},jwks:"..."} -POST /jwt_validation_rules # Rule: {name,hostname,token_validation_id,action:"block"} -``` - -## Workers Integration - -### Access JWT Claims -```js -export default { - async fetch(req, env) { - // Access validated JWT payload - const jwt = req.cf?.jwt?.payload?.[env.JWT_CONFIG_ID]?.[0]; - if (jwt) { - const userId = jwt.sub; - const role = jwt.role; - } - } -} -``` - -### Access mTLS Info -```js -export default { - async fetch(req, env) { - const tls = req.cf?.tlsClientAuth; - if (tls?.certVerified === 'SUCCESS') { - const fingerprint = tls.certFingerprintSHA256; - // Authenticated client - } - } -} -``` - -### Dynamic JWKS Update -```js -export default { - async scheduled(event, env) { - const jwks = await (await fetch('https://auth.example.com/.well-known/jwks.json')).json(); - await fetch(`https://api.cloudflare.com/client/v4/zones/${env.ZONE_ID}/api_gateway/token_validation/${env.CONFIG_ID}`, { - method: 'PATCH', - headers: {'Authorization': `Bearer ${env.CF_API_TOKEN}`, 'Content-Type': 'application/json'}, - body: JSON.stringify({jwks: JSON.stringify(jwks)}) - }); - } -} -``` - -## Firewall Fields - -### Core Fields -```js -cf.api_gateway.auth_id_present // Session ID present -cf.api_gateway.request_violates_schema // Schema violation -cf.api_gateway.fallthrough_triggered // No endpoint match -cf.tls_client_auth.cert_verified // mTLS cert valid -cf.tls_client_auth.cert_fingerprint_sha256 -``` - -### JWT Validation (2026) -```js -// Modern validation syntax -is_jwt_valid(http.request.jwt.payload["{config_id}"][0]) - -// Legacy (still supported) -cf.api_gateway.jwt_claims_valid - -// Extract claims -lookup_json_string(http.request.jwt.payload["{config_id}"][0], "claim_name") -``` - -### Risk Labels (2026) -```js -// BOLA detection -cf.api_gateway.cf-risk-bola-enumeration // Sequential resource access detected -cf.api_gateway.cf-risk-bola-pollution // Parameter pollution detected - -// Authentication posture -cf.api_gateway.cf-risk-missing-auth // Endpoint lacks authentication -cf.api_gateway.cf-risk-mixed-auth // Inconsistent auth patterns -``` - -## BOLA Detection - -```bash -GET /user_schemas/{schema_id}/bola # Get BOLA config -PATCH /user_schemas/{schema_id}/bola # Update: {enabled:true} -``` - -## Auth Posture - -```bash -GET /discovery/authentication_posture # List unprotected endpoints -``` - -## GraphQL Protection - -```bash -GET /settings/graphql_protection # Get limits -PUT /settings/graphql_protection # Set: {max_depth,max_size} -``` - -## See Also - -- [configuration.md](configuration.md) - Setup guides for all features -- [patterns.md](patterns.md) - Firewall rules and common patterns -- [API Gateway API Docs](https://developers.cloudflare.com/api/resources/api_gateway/) diff --git a/external/develop/devops/cloudflare/references/api-shield/configuration.md b/external/develop/devops/cloudflare/references/api-shield/configuration.md deleted file mode 100644 index f95744c..0000000 --- a/external/develop/devops/cloudflare/references/api-shield/configuration.md +++ /dev/null @@ -1,192 +0,0 @@ -# Configuration - -## Schema Validation 2.0 Setup - -> ⚠️ **Classic Schema Validation deprecated.** Use Schema Validation 2.0. - -**Upload schema (Dashboard):** -``` -Security > API Shield > Schema Validation > Add validation -- Upload .yml/.yaml/.json (OpenAPI v3.0) -- Endpoints auto-added to Endpoint Management -- Action: Log | Block | None -- Body inspection: JSON payloads -``` - -**Change validation action:** -``` -Security > API Shield > Settings > Schema Validation -Per-endpoint: Filter → ellipses → Change action -Default action: Set global mitigation action -``` - -**Migration from Classic:** -``` -1. Export existing schema (if available) -2. Delete all Classic schema validation rules -3. Wait 5 min for cache clear -4. Re-upload via Schema Validation 2.0 interface -5. Verify in Security > Events -``` - -**Fallthrough rule** (catch-all unknown endpoints): -``` -Security > API Shield > Settings > Fallthrough > Use Template -- Select hostnames -- Create rule with cf.api_gateway.fallthrough_triggered -- Action: Log (discover) or Block (strict) -``` - -**Body inspection:** Supports `application/json`, `*/*`, `application/*`. Disable origin MIME sniffing to prevent bypasses. - -## JWT Validation - -**Setup token config:** -``` -Security > API Shield > Settings > JWT Settings > Add configuration -- Name: "Auth0 JWT Config" -- Location: Header/Cookie + name (e.g., "Authorization") -- JWKS: Paste public keys from IdP -``` - -**Create validation rule:** -``` -Security > API Shield > API Rules > Add rule -- Hostname: api.example.com -- Deselect endpoints to ignore -- Token config: Select config -- Enforce presence: Ignore or Mark as non-compliant -- Action: Log/Block/Challenge -``` - -**Rate limit by JWT claim:** -```wirefilter -lookup_json_string(http.request.jwt.claims["{config_id}"][0], "sub") -``` - -**Special cases:** -- Two JWTs, different IdPs: Create 2 configs, select both, "Validate all" -- IdP migration: 2 configs + 2 rules, adjust actions per state -- Bearer prefix: API Shield handles with/without -- Nested claims: Dot notation `user.email` - -## Mutual TLS (mTLS) - -**Setup:** -``` -SSL/TLS > Client Certificates > Create Certificate -- Generate CF-managed CA (all plans) -- Upload custom CA (Enterprise, max 5) -``` - -**Configure mTLS rule:** -``` -Security > API Shield > mTLS -- Select hostname(s) -- Choose certificate(s) -- Action: Block/Log/Challenge -``` - -**Test:** -```bash -openssl req -x509 -newkey rsa:4096 -keyout client-key.pem -out client-cert.pem -days 365 -curl https://api.example.com/endpoint --cert client-cert.pem --key client-key.pem -``` - -## Session Identifiers - -Critical for BOLA Detection, Sequence Mitigation, and analytics. Configure header/cookie that uniquely IDs API users. - -**Examples:** JWT sub claim, session token, API key, custom user ID header - -**Configure:** -``` -Security > API Shield > Settings > Session Identifiers -- Type: Header/Cookie -- Name: "X-User-ID" or "Authorization" -``` - -## BOLA Detection - -Detects Broken Object Level Authorization attacks (enumeration + parameter pollution). - -**Enable:** -``` -Security > API Shield > Schema Validation > [Select Schema] > BOLA Detection -- Enable detection -- Threshold: Sensitivity level (Low/Medium/High) -- Action: Log or Block -``` - -**Requirements:** -- Schema Validation 2.0 enabled -- Session identifiers configured -- Minimum traffic: 1000+ requests/day per endpoint - -## Authentication Posture - -Identifies unprotected or inconsistently protected endpoints. - -**View report:** -``` -Security > API Shield > Authentication Posture -- Shows endpoints lacking JWT/mTLS -- Highlights mixed authentication patterns -``` - -**Remediate:** -1. Review flagged endpoints -2. Add JWT validation rules -3. Configure mTLS for sensitive endpoints -4. Monitor posture score - -## Volumetric Abuse + GraphQL - -**Volumetric Abuse Detection:** -`Security > API Shield > Settings > Volumetric Abuse Detection` -- Enable per-endpoint monitoring, set thresholds, action: Log | Challenge | Block - -**GraphQL Protection:** -`Security > API Shield > Settings > GraphQL Protection` -- Max query depth: 10, max size: 100KB, block introspection (production) - -## Terraform - -```hcl -# Session identifier -resource "cloudflare_api_shield" "main" { - zone_id = var.zone_id - auth_id_characteristics { - type = "header" - name = "Authorization" - } -} - -# Add endpoint -resource "cloudflare_api_shield_operation" "users_get" { - zone_id = var.zone_id - method = "GET" - host = "api.example.com" - endpoint = "/api/users/{id}" -} - -# JWT validation rule -resource "cloudflare_ruleset" "jwt_validation" { - zone_id = var.zone_id - name = "API JWT Validation" - kind = "zone" - phase = "http_request_firewall_custom" - - rules { - action = "block" - expression = "(http.host eq \"api.example.com\" and not is_jwt_valid(http.request.jwt.payload[\"{config_id}\"][0]))" - description = "Block invalid JWTs" - } -} -``` - -## See Also - -- [api.md](api.md) - API endpoints and Workers integration -- [patterns.md](patterns.md) - Firewall rules and deployment patterns -- [gotchas.md](gotchas.md) - Troubleshooting and limits diff --git a/external/develop/devops/cloudflare/references/api-shield/gotchas.md b/external/develop/devops/cloudflare/references/api-shield/gotchas.md deleted file mode 100644 index 255517f..0000000 --- a/external/develop/devops/cloudflare/references/api-shield/gotchas.md +++ /dev/null @@ -1,125 +0,0 @@ -# Gotchas & Troubleshooting - -## Common Errors - -### "Schema Validation 2.0 not working after migration" - -**Cause:** Classic rules still active, conflicting with new system -**Solution:** -1. Delete ALL Classic schema validation rules -2. Clear Cloudflare cache (wait 5 min) -3. Re-upload schema via new Schema Validation 2.0 interface -4. Verify in Security > Events -5. Check action is set (Log/Block) - -### "Schema validation blocking valid requests" - -**Cause:** Schema too restrictive, missing fields, or incorrect types -**Solution:** -1. Check Firewall Events for violation details -2. Review schema in Settings -3. Test schema in Swagger Editor -4. Use Log mode to validate before blocking -5. Update schema with correct specifications -6. Ensure Schema Validation 2.0 (not Classic) - -### "JWT validation failing" - -**Cause:** JWKS mismatch with IdP, expired token, wrong header/cookie name, or clock skew -**Solution:** -1. Verify JWKS matches IdP configuration -2. Check token `exp` claim is valid -3. Confirm header/cookie name matches config -4. Test token at jwt.io -5. Account for clock skew (±5 min tolerance) -6. Use modern syntax: `is_jwt_valid(http.request.jwt.payload["{config_id}"][0])` - -### "BOLA detection false positives" - -**Cause:** Legitimate sequential access patterns, bulk operations, or sensitivity too high -**Solution:** -1. Review BOLA events in Security > Events -2. Lower sensitivity threshold (High → Medium → Low) -3. Exclude legitimate bulk operations from detection -4. Ensure session identifiers uniquely identify users -5. Verify minimum traffic requirements met (1000+ req/day) - -### "Risk labels not appearing in firewall rules" - -**Cause:** Feature not enabled, insufficient traffic, or missing session identifiers -**Solution:** -1. Verify Schema Validation 2.0 enabled -2. Enable BOLA Detection in schema settings -3. Configure session identifiers (required for BOLA) -4. Wait 24-48h for ML model training -5. Check minimum traffic thresholds met - -### "Endpoint discovery not finding APIs" - -**Cause:** Insufficient traffic (<500 reqs/10d), non-2xx responses, Worker direct requests, or incorrect session ID config -**Solution:** Ensure 500+ requests in 10 days, 2xx responses from edge (not Workers direct), configure session IDs correctly. ML updates daily. - -### "Sequence detection false positives" - -**Cause:** Lookback window issues, non-unique session IDs, or model sensitivity -**Solution:** -1. Review lookback settings (10 reqs to managed endpoints, 10min window) -2. Ensure session ID uniqueness per user (not shared tokens) -3. Adjust positive/negative model balance -4. Exclude legitimate workflows from detection - -### "GraphQL protection blocking valid queries" - -**Cause:** Query depth/size limits too restrictive, complex but legitimate queries -**Solution:** -1. Review blocked query patterns in Security > Events -2. Increase max_depth (default: 10) if needed -3. Increase max_size (default: 100KB) for complex queries -4. Whitelist specific query signatures -5. Use Log mode to tune before blocking - -### "Token invalid" - -**Cause:** Configuration error, JWKS mismatch, or expired token -**Solution:** Verify config matches IdP, update JWKS, check token expiration - -### "Schema violation" - -**Cause:** Missing required fields, wrong data types, or spec mismatch -**Solution:** Review schema against actual requests, ensure all required fields present, validate types match spec - -### "Fallthrough" - -**Cause:** Unknown endpoint or pattern mismatch -**Solution:** Update schema with all endpoints, check path pattern matching - -### "mTLS failed" - -**Cause:** Certificate untrusted/expired or wrong CA -**Solution:** Verify cert chain, check expiration, confirm correct CA uploaded - -## Limits (2026) - -| Resource/Limit | Value | Notes | -|----------------|-------|-------| -| OpenAPI version | v3.0.x only | No external refs, must be valid | -| Schema operations | 10K (Enterprise) | Contact for higher limits | -| JWT validation sources | Headers/cookies only | No query params/body | -| Endpoint discovery | 500+ reqs/10d | Minimum for ML model | -| Path normalization | Automatic | `/profile/238` → `/profile/{var1}` | -| Schema parameters | No `content` field | No object param validation | -| BOLA detection | 1000+ reqs/day/endpoint | Per-endpoint minimum | -| Session ID uniqueness | Required | BOLA/Sequence need unique IDs | -| GraphQL max depth | 1-50 | Default: 10 | -| GraphQL max size | 1KB-1MB | Default: 100KB | -| JWT claim nesting | 10 levels max | Use dot notation | -| mTLS CA certificates | 5 custom max | CF-managed unlimited | -| Schema upload size | 5MB max | Compressed OpenAPI spec | -| Volumetric abuse baseline | 7 days training | Initial ML period | -| Auth Posture refresh | Daily | Updated nightly | - -## See Also - -- [configuration.md](configuration.md) - Setup guides to avoid common issues -- [patterns.md](patterns.md) - Best practices and progressive rollout -- [API Shield Docs](https://developers.cloudflare.com/api-shield/) diff --git a/external/develop/devops/cloudflare/references/api-shield/patterns.md b/external/develop/devops/cloudflare/references/api-shield/patterns.md deleted file mode 100644 index 5721dba..0000000 --- a/external/develop/devops/cloudflare/references/api-shield/patterns.md +++ /dev/null @@ -1,180 +0,0 @@ -# Patterns & Use Cases - -## Protect API with Schema + JWT - -```bash -# 1. Upload OpenAPI schema -POST /zones/{zone_id}/api_gateway/user_schemas - -# 2. Configure JWT validation -POST /zones/{zone_id}/api_gateway/token_validation -{ - "name": "Auth0", - "location": {"header": "Authorization"}, - "jwks": "{...}" -} - -# 3. Create JWT rule -POST /zones/{zone_id}/api_gateway/jwt_validation_rules - -# 4. Set schema validation action -PUT /zones/{zone_id}/api_gateway/settings/schema_validation -{"validation_default_mitigation_action": "block"} -``` - -## Progressive Rollout - -``` -1. Log mode: Observe false positives - - Schema: Action = Log - - JWT: Action = Log - -2. Block subset: Protect critical endpoints - - Change specific endpoint actions to Block - - Monitor firewall events - -3. Full enforcement: Block all violations - - Change default action to Block - - Handle fallthrough with custom rule -``` - -## BOLA Detection - -### Enumeration Detection -Detects sequential resource access (e.g., `/users/1`, `/users/2`, `/users/3`). - -```javascript -// Block BOLA enumeration attempts -(cf.api_gateway.cf-risk-bola-enumeration and http.host eq "api.example.com") -// Action: Block or Challenge -``` - -### Parameter Pollution -Detects duplicate/excessive parameters in requests. - -```javascript -// Block parameter pollution -(cf.api_gateway.cf-risk-bola-pollution and http.host eq "api.example.com") -// Action: Block -``` - -### Combined BOLA Protection -```javascript -// Comprehensive BOLA rule -(cf.api_gateway.cf-risk-bola-enumeration or cf.api_gateway.cf-risk-bola-pollution) -and http.host eq "api.example.com" -// Action: Block -``` - -## Authentication Posture - -### Detect Missing Auth -```javascript -// Log endpoints lacking authentication -(cf.api_gateway.cf-risk-missing-auth and http.host eq "api.example.com") -// Action: Log (for audit) -``` - -### Detect Mixed Auth -```javascript -// Alert on inconsistent auth patterns -(cf.api_gateway.cf-risk-mixed-auth and http.host eq "api.example.com") -// Action: Log (review required) -``` - -## Fallthrough Detection (Shadow APIs) - -```javascript -// WAF Custom Rule -(cf.api_gateway.fallthrough_triggered and http.host eq "api.example.com") -// Action: Log (discover unknown) or Block (strict) -``` - -## Rate Limiting by User - -```javascript -// Rate Limiting Rule (modern syntax) -(http.host eq "api.example.com" and - is_jwt_valid(http.request.jwt.payload["{config_id}"][0])) - -// Rate: 100 req/60s -// Counting expression: lookup_json_string(http.request.jwt.payload["{config_id}"][0], "sub") -``` - -## Volumetric Abuse Response - -```javascript -// Detect abnormal traffic spikes -(cf.api_gateway.volumetric_abuse_detected and http.host eq "api.example.com") -// Action: Challenge or Rate Limit - -// Combined with rate limiting -(cf.api_gateway.volumetric_abuse_detected or - cf.threat_score gt 50) and http.host eq "api.example.com" -// Action: JS Challenge -``` - -## GraphQL Protection - -```javascript -// Block oversized queries -(http.request.uri.path eq "/graphql" and - cf.api_gateway.graphql_query_size gt 100000) -// Action: Block - -// Block deep nested queries -(http.request.uri.path eq "/graphql" and - cf.api_gateway.graphql_query_depth gt 10) -// Action: Block -``` - -## Architecture Patterns - -**Public API:** Discovery + Schema Validation 2.0 + JWT + Rate Limiting + Bot Management -**Partner API:** mTLS + Schema Validation + Sequence Mitigation -**Internal API:** Discovery + Schema Learning + Auth Posture - -## OWASP API Security Top 10 Mapping (2026) - -| OWASP Issue | API Shield Solutions | -|-------------|---------------------| -| API1:2023 Broken Object Level Authorization | **BOLA Detection** (enumeration + pollution), Sequence mitigation, Schema, JWT, Rate Limiting | -| API2:2023 Broken Authentication | **Auth Posture**, mTLS, JWT validation, Bot Management | -| API3:2023 Broken Object Property Auth | Schema validation, JWT validation | -| API4:2023 Unrestricted Resource Access | Rate Limiting, **Volumetric Abuse Detection**, **GraphQL Protection**, Bot Management | -| API5:2023 Broken Function Level Auth | Schema validation, JWT validation, Auth Posture | -| API6:2023 Unrestricted Business Flows | Sequence mitigation, Bot Management | -| API7:2023 SSRF | Schema validation, WAF managed rules | -| API8:2023 Security Misconfiguration | **Schema Validation 2.0**, Auth Posture, WAF rules | -| API9:2023 Improper Inventory Management | **API Discovery**, Schema learning, Auth Posture | -| API10:2023 Unsafe API Consumption | JWT validation, Schema validation, WAF managed | - -## Monitoring - -**Security Events:** `Security > Events` → Filter: Action = block, Service = API Shield -**Firewall Analytics:** `Analytics > Security` → Filter by `cf.api_gateway.*` fields -**Logpush fields:** APIGatewayAuthIDPresent, APIGatewayRequestViolatesSchema, APIGatewayFallthroughDetected, JWTValidationResult - -## Availability (2026) - -| Feature | Availability | Notes | -|---------|-------------|-------| -| mTLS (CF-managed CA) | All plans | Self-service | -| Endpoint Management | All plans | Limited operations | -| Schema Validation 2.0 | All plans | Limited operations | -| API Discovery | Enterprise | 10K+ ops | -| JWT Validation | Enterprise add-on | Full validation | -| BOLA Detection | Enterprise add-on | Requires session IDs | -| Auth Posture | Enterprise add-on | Security audit | -| Volumetric Abuse Detection | Enterprise add-on | Traffic analysis | -| GraphQL Protection | Enterprise add-on | Query limits | -| Sequence Mitigation | Enterprise (beta) | Contact team | -| Full Suite | Enterprise add-on | All features | - -**Enterprise limits:** 10K operations (contact for higher). Preview access available for non-contract evaluation. - -## See Also - -- [configuration.md](configuration.md) - Setup all features before creating rules -- [api.md](api.md) - Firewall field reference and API endpoints -- [gotchas.md](gotchas.md) - Common issues and limits diff --git a/external/develop/devops/cloudflare/references/api/README.md b/external/develop/devops/cloudflare/references/api/README.md deleted file mode 100644 index 8310691..0000000 --- a/external/develop/devops/cloudflare/references/api/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Cloudflare API Integration - -Guide for working with Cloudflare's REST API - authentication, SDK usage, common patterns, and troubleshooting. - -## Quick Decision Tree - -``` -How are you calling the Cloudflare API? -├─ From Workers runtime → Use bindings, not REST API (see ../bindings/) -├─ Server-side (Node/Python/Go) → Official SDK (see api.md) -├─ CLI/scripts → Wrangler or curl (see configuration.md) -├─ Infrastructure-as-code → See ../pulumi/ or ../terraform/ -└─ One-off requests → curl examples (see api.md) -``` - -## SDK Selection - -| Language | Package | Best For | Default Retries | -|----------|---------|----------|-----------------| -| TypeScript | `cloudflare` | Node.js, Bun, Next.js, Workers | 2 | -| Python | `cloudflare` | FastAPI, Django, scripts | 2 | -| Go | `cloudflare-go/v4` | CLI tools, microservices | 10 | - -All SDKs are Stainless-generated from OpenAPI spec (consistent APIs). - -## Authentication Methods - -| Method | Security | Use Case | Scope | -|--------|----------|----------|-------| -| **API Token** ✓ | Scoped, rotatable | Production | Per-zone or account | -| API Key + Email | Full account access | Legacy only | Everything | -| User Service Key | Limited | Origin CA certs only | Origin CA | - -**Always use API tokens** for new projects. - -## Rate Limits - -| Limit | Value | -|-------|-------| -| Per user/token | 1200 requests / 5 minutes | -| Per IP | 200 requests / second | -| GraphQL | 320 / 5 minutes (cost-based) | - -## Reading Order - -| Task | Files to Read | -|------|---------------| -| Initialize SDK client | api.md | -| Configure auth/timeout/retry | configuration.md | -| Find usage patterns | patterns.md | -| Debug errors/rate limits | gotchas.md | -| Product-specific APIs | [Workers docs](https://developers.cloudflare.com/workers/), ../r2/, ../kv/, etc. | - -## In This Reference - -- **[api.md](api.md)** - SDK client initialization, pagination, error handling, examples -- **[configuration.md](configuration.md)** - Environment variables, SDK config, Wrangler setup -- **[patterns.md](patterns.md)** - Real-world patterns, batch operations, workflows -- **[gotchas.md](gotchas.md)** - Rate limits, SDK-specific issues, troubleshooting - -## See Also - -- [Cloudflare API Docs](https://developers.cloudflare.com/api/) -- [Bindings Reference](../bindings/) - Workers runtime bindings (preferred over REST API) -- [Wrangler Reference](https://developers.cloudflare.com/workers/wrangler/) - CLI tool for Cloudflare development -- [GraphQL Analytics API Reference](../graphql-api/) - Analytics data via GraphQL (separate endpoint from REST API) diff --git a/external/develop/devops/cloudflare/references/api/api.md b/external/develop/devops/cloudflare/references/api/api.md deleted file mode 100644 index 3371014..0000000 --- a/external/develop/devops/cloudflare/references/api/api.md +++ /dev/null @@ -1,204 +0,0 @@ -# API Reference - -## Client Initialization - -### TypeScript - -```typescript -import Cloudflare from 'cloudflare'; - -const client = new Cloudflare({ - apiToken: process.env.CLOUDFLARE_API_TOKEN, -}); -``` - -### Python - -```python -from cloudflare import Cloudflare - -client = Cloudflare(api_token=os.environ.get("CLOUDFLARE_API_TOKEN")) - -# For async: -from cloudflare import AsyncCloudflare -client = AsyncCloudflare(api_token=os.environ["CLOUDFLARE_API_TOKEN"]) -``` - -### Go - -```go -import ( - "github.com/cloudflare/cloudflare-go/v4" - "github.com/cloudflare/cloudflare-go/v4/option" -) - -client := cloudflare.NewClient( - option.WithAPIToken(os.Getenv("CLOUDFLARE_API_TOKEN")), -) -``` - -## Authentication - -### API Token (Recommended) - -**Create token**: Dashboard → My Profile → API Tokens → Create Token - -```bash -export CLOUDFLARE_API_TOKEN='your-token-here' - -curl "https://api.cloudflare.com/client/v4/zones" \ - --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -``` - -**Token scopes**: Always use minimal permissions (zone-specific, time-limited). - -### API Key (Legacy) - -```bash -curl "https://api.cloudflare.com/client/v4/zones" \ - --header "X-Auth-Email: user@example.com" \ - --header "X-Auth-Key: $CLOUDFLARE_API_KEY" -``` - -**Not recommended:** Full account access, cannot scope permissions. - -## Auto-Pagination - -All SDKs support automatic pagination for list operations. - -```typescript -// TypeScript: for await...of -for await (const zone of client.zones.list()) { - console.log(zone.id); -} -``` - -```python -# Python: iterator protocol -for zone in client.zones.list(): - print(zone.id) -``` - -```go -// Go: ListAutoPaging -iter := client.Zones.ListAutoPaging(ctx, cloudflare.ZoneListParams{}) -for iter.Next() { - zone := iter.Current() - fmt.Println(zone.ID) -} -``` - -## Error Handling - -```typescript -try { - const zone = await client.zones.get({ zone_id: 'xxx' }); -} catch (err) { - if (err instanceof Cloudflare.NotFoundError) { - // 404 - } else if (err instanceof Cloudflare.RateLimitError) { - // 429 - SDK auto-retries with backoff - } else if (err instanceof Cloudflare.APIError) { - console.log(err.status, err.message); - } -} -``` - -**Common Error Types:** -- `AuthenticationError` (401) - Invalid token -- `PermissionDeniedError` (403) - Insufficient scope -- `NotFoundError` (404) - Resource not found -- `RateLimitError` (429) - Rate limit exceeded -- `InternalServerError` (≥500) - Cloudflare error - -## Zone Management - -```typescript -// List zones -const zones = await client.zones.list({ - account: { id: 'account-id' }, - status: 'active', -}); - -// Create zone -const zone = await client.zones.create({ - account: { id: 'account-id' }, - name: 'example.com', - type: 'full', // or 'partial' -}); - -// Update zone -await client.zones.edit('zone-id', { - paused: false, -}); - -// Delete zone -await client.zones.delete('zone-id'); -``` - -```go -// Go: requires cloudflare.F() wrapper -zone, err := client.Zones.New(ctx, cloudflare.ZoneNewParams{ - Account: cloudflare.F(cloudflare.ZoneNewParamsAccount{ - ID: cloudflare.F("account-id"), - }), - Name: cloudflare.F("example.com"), - Type: cloudflare.F(cloudflare.ZoneNewParamsTypeFull), -}) -``` - -## DNS Management - -```typescript -// Create DNS record -await client.dns.records.create({ - zone_id: 'zone-id', - type: 'A', - name: 'subdomain.example.com', - content: '192.0.2.1', - ttl: 1, // auto - proxied: true, // Orange cloud -}); - -// List DNS records (with auto-pagination) -for await (const record of client.dns.records.list({ - zone_id: 'zone-id', - type: 'A', -})) { - console.log(record.name, record.content); -} - -// Update DNS record -await client.dns.records.update({ - zone_id: 'zone-id', - dns_record_id: 'record-id', - type: 'A', - name: 'subdomain.example.com', - content: '203.0.113.1', - proxied: true, -}); - -// Delete DNS record -await client.dns.records.delete({ - zone_id: 'zone-id', - dns_record_id: 'record-id', -}); -``` - -```python -# Python example -client.dns.records.create( - zone_id="zone-id", - type="A", - name="subdomain.example.com", - content="192.0.2.1", - ttl=1, - proxied=True, -) -``` - -## See Also - -- [configuration.md](./configuration.md) - SDK configuration, environment variables -- [patterns.md](./patterns.md) - Real-world patterns and workflows -- [gotchas.md](./gotchas.md) - Rate limits, troubleshooting diff --git a/external/develop/devops/cloudflare/references/api/configuration.md b/external/develop/devops/cloudflare/references/api/configuration.md deleted file mode 100644 index 05484ec..0000000 --- a/external/develop/devops/cloudflare/references/api/configuration.md +++ /dev/null @@ -1,160 +0,0 @@ -# Configuration - -## Environment Variables - -### Set Variables - -| Platform | Command | -|----------|---------| -| Linux/macOS | `export CLOUDFLARE_API_TOKEN='token'` | -| PowerShell | `$env:CLOUDFLARE_API_TOKEN = 'token'` | -| Windows CMD | `set CLOUDFLARE_API_TOKEN=token` | - -**Security:** Never commit tokens. Use `.env` files (gitignored) or secret managers. - -### .env File Pattern - -```bash -# .env (add to .gitignore) -CLOUDFLARE_API_TOKEN=your-token-here -CLOUDFLARE_ACCOUNT_ID=your-account-id -``` - -```typescript -// TypeScript -import 'dotenv/config'; - -const client = new Cloudflare({ - apiToken: process.env.CLOUDFLARE_API_TOKEN, -}); -``` - -```python -# Python -from dotenv import load_dotenv -load_dotenv() - -client = Cloudflare(api_token=os.environ["CLOUDFLARE_API_TOKEN"]) -``` - -## SDK Configuration - -### TypeScript - -```typescript -const client = new Cloudflare({ - apiToken: process.env.CLOUDFLARE_API_TOKEN, - timeout: 120000, // 2 min (default 60s), in milliseconds - maxRetries: 5, // default 2 - baseURL: 'https://...', // proxy (rare) -}); - -// Per-request overrides -await client.zones.get( - { zone_id: 'zone-id' }, - { timeout: 5000, maxRetries: 0 } -); -``` - -### Python - -```python -client = Cloudflare( - api_token=os.environ["CLOUDFLARE_API_TOKEN"], - timeout=120, # seconds (default 60) - max_retries=5, # default 2 - base_url="https://...", # proxy (rare) -) - -# Per-request overrides -client.with_options(timeout=5, max_retries=0).zones.get(zone_id="zone-id") -``` - -### Go - -```go -client := cloudflare.NewClient( - option.WithAPIToken(os.Getenv("CLOUDFLARE_API_TOKEN")), - option.WithMaxRetries(5), // default 10 (higher than TS/Python) - option.WithRequestTimeout(2 * time.Minute), // default 60s - option.WithBaseURL("https://..."), // proxy (rare) -) - -// Per-request overrides -client.Zones.Get(ctx, "zone-id", option.WithMaxRetries(0)) -``` - -## Configuration Options - -| Option | TypeScript | Python | Go | Default | -|--------|-----------|--------|-----|---------| -| Timeout | `timeout` (ms) | `timeout` (s) | `WithRequestTimeout` | 60s | -| Retries | `maxRetries` | `max_retries` | `WithMaxRetries` | 2 (Go: 10) | -| Base URL | `baseURL` | `base_url` | `WithBaseURL` | api.cloudflare.com | - -**Note:** Go SDK has higher default retries (10) than TypeScript/Python (2). - -## Timeout Configuration - -**When to increase:** -- Large zone transfers -- Bulk DNS operations -- Worker script uploads - -```typescript -const client = new Cloudflare({ - timeout: 300000, // 5 minutes -}); -``` - -## Retry Configuration - -**When to increase:** Rate-limit-heavy workflows, flaky network - -**When to decrease:** Fast-fail requirements, user-facing requests - -```typescript -// Increase retries for batch operations -const client = new Cloudflare({ maxRetries: 10 }); - -// Disable retries for fast-fail -const fastClient = new Cloudflare({ maxRetries: 0 }); -``` - -## Wrangler CLI Integration - -```bash -# Configure authentication -wrangler login -# Or -export CLOUDFLARE_API_TOKEN='token' - -# Common commands that use API -wrangler deploy # Uploads worker via API -wrangler kv:key put # KV operations -wrangler r2 bucket create # R2 operations -wrangler d1 execute # D1 operations -wrangler pages deploy # Pages operations - -# Get API configuration -wrangler whoami # Shows authenticated user -``` - -### wrangler.toml - -```toml -name = "my-worker" -main = "src/index.ts" -compatibility_date = "2024-01-01" -account_id = "your-account-id" - -# Can also use env vars: -# CLOUDFLARE_ACCOUNT_ID -# CLOUDFLARE_API_TOKEN -``` - -## See Also - -- [api.md](./api.md) - Client initialization, authentication -- [gotchas.md](./gotchas.md) - Rate limits, timeout errors -- [Wrangler Reference](https://developers.cloudflare.com/workers/wrangler/) - CLI tool details diff --git a/external/develop/devops/cloudflare/references/api/gotchas.md b/external/develop/devops/cloudflare/references/api/gotchas.md deleted file mode 100644 index e6666dc..0000000 --- a/external/develop/devops/cloudflare/references/api/gotchas.md +++ /dev/null @@ -1,225 +0,0 @@ -# Gotchas & Troubleshooting - -## Rate Limits & 429 Errors - -**Actual Limits:** -- **1200 requests / 5 minutes** per user/token (global) -- **200 requests / second** per IP address -- **GraphQL: 320 / 5 minutes** (cost-based) - -**SDK Behavior:** -- Auto-retry with exponential backoff (default 2 retries, Go: 10) -- Respects `Retry-After` header -- Throws `RateLimitError` after exhausting retries - -**Solution:** - -```typescript -// Increase retries for rate-limit-heavy workflows -const client = new Cloudflare({ maxRetries: 5 }); - -// Add application-level throttling -import pLimit from 'p-limit'; -const limit = pLimit(10); // Max 10 concurrent requests -``` - -## SDK-Specific Issues - -### Go: Required Field Wrapper - -**Problem:** Go SDK requires `cloudflare.F()` wrapper for optional fields. - -```go -// ❌ WRONG - Won't compile or send field -client.Zones.New(ctx, cloudflare.ZoneNewParams{ - Name: "example.com", -}) - -// ✅ CORRECT -client.Zones.New(ctx, cloudflare.ZoneNewParams{ - Name: cloudflare.F("example.com"), - Account: cloudflare.F(cloudflare.ZoneNewParamsAccount{ - ID: cloudflare.F("account-id"), - }), -}) -``` - -**Why:** Distinguishes between zero value, null, and omitted fields. - -### Python: Async vs Sync Clients - -**Problem:** Using sync client in async context or vice versa. - -```python -# ❌ WRONG - Can't await sync client -from cloudflare import Cloudflare -client = Cloudflare() -await client.zones.list() # TypeError - -# ✅ CORRECT - Use AsyncCloudflare -from cloudflare import AsyncCloudflare -client = AsyncCloudflare() -await client.zones.list() -``` - -## Token Permission Errors (403) - -**Problem:** API returns 403 Forbidden despite valid token. - -**Cause:** Token lacks required permissions (scope). - -**Scopes Required:** - -| Operation | Required Scope | -|-----------|----------------| -| List zones | Zone:Read (zone-level or account-level) | -| Create zone | Zone:Edit (account-level) | -| Edit DNS | DNS:Edit (zone-level) | -| Deploy Worker | Workers Script:Edit (account-level) | -| Read KV | Workers KV Storage:Read | -| Write KV | Workers KV Storage:Edit | - -**Solution:** Re-create token with correct permissions in Dashboard → My Profile → API Tokens. - -## Pagination Truncation - -**Problem:** Only getting first 20 results (default page size). - -**Solution:** Use auto-pagination iterators. - -```typescript -// ❌ WRONG - Only first page (20 items) -const page = await client.zones.list(); - -// ✅ CORRECT - All results -const zones = []; -for await (const zone of client.zones.list()) { - zones.push(zone); -} -``` - -## Workers Subrequests - -**Problem:** Rate limit hit faster than expected in Workers. - -**Cause:** Workers subrequests count as separate API calls. - -**Solution:** Use bindings instead of REST API in Workers (see ../bindings/). - -```typescript -// ❌ WRONG - REST API in Workers (counts against rate limit) -const client = new Cloudflare({ apiToken: env.CLOUDFLARE_API_TOKEN }); -const zones = await client.zones.list(); - -// ✅ CORRECT - Use bindings (no rate limit) -// Access via env.MY_BINDING -``` - -## Authentication Errors (401) - -**Problem:** "Authentication failed" or "Invalid token" - -**Causes:** -- Token expired -- Token deleted/revoked -- Token not set in environment -- Wrong token format - -**Solution:** - -```typescript -// Verify token is set -if (!process.env.CLOUDFLARE_API_TOKEN) { - throw new Error('CLOUDFLARE_API_TOKEN not set'); -} - -// Test token -const user = await client.user.tokens.verify(); -console.log('Token valid:', user.status); -``` - -## Timeout Errors - -**Problem:** Request times out (default 60s). - -**Cause:** Large operations (bulk DNS, zone transfers). - -**Solution:** Increase timeout or split operations. - -```typescript -// Increase timeout -const client = new Cloudflare({ - timeout: 300000, // 5 minutes -}); - -// Or split operations -const batchSize = 100; -for (let i = 0; i < records.length; i += batchSize) { - const batch = records.slice(i, i + batchSize); - await processBatch(batch); -} -``` - -## Zone Not Found (404) - -**Problem:** Zone ID valid but returns 404. - -**Causes:** -- Zone not in account associated with token -- Zone deleted -- Wrong zone ID format - -**Solution:** - -```typescript -// List all zones to find correct ID -for await (const zone of client.zones.list()) { - console.log(zone.id, zone.name); -} -``` - -## Limits Reference - -| Resource/Limit | Value | Notes | -|----------------|-------|-------| -| API rate limit | 1200/5min | Per user/token | -| IP rate limit | 200/sec | Per IP | -| GraphQL rate limit | 320/5min | Cost-based | -| Parallel requests (recommended) | < 10 | Avoid overwhelming API | -| Default page size | 20 | Use auto-pagination | -| Max page size | 50 | Some endpoints | - -## Best Practices - -**Security:** -- Never commit tokens -- Use minimal permissions -- Rotate tokens regularly -- Set token expiration - -**Performance:** -- Batch operations -- Use pagination wisely -- Cache responses -- Handle rate limits - -**Code Organization:** - -```typescript -// Create reusable client instance -export const cfClient = new Cloudflare({ - apiToken: process.env.CLOUDFLARE_API_TOKEN, - maxRetries: 5, -}); - -// Wrap common operations -export async function getZoneDetails(zoneId: string) { - return await cfClient.zones.get({ zone_id: zoneId }); -} -``` - -## See Also - -- [api.md](./api.md) - Error types, authentication -- [configuration.md](./configuration.md) - Timeout/retry configuration -- [patterns.md](./patterns.md) - Error handling patterns diff --git a/external/develop/devops/cloudflare/references/api/patterns.md b/external/develop/devops/cloudflare/references/api/patterns.md deleted file mode 100644 index 3c7c693..0000000 --- a/external/develop/devops/cloudflare/references/api/patterns.md +++ /dev/null @@ -1,204 +0,0 @@ -# Common Patterns - -## List All with Auto-Pagination - -**Problem:** API returns paginated results. Default page size is 20. - -**Solution:** Use SDK auto-pagination to iterate all results. - -```typescript -// TypeScript -for await (const zone of client.zones.list()) { - console.log(zone.name); -} -``` - -```python -# Python -for zone in client.zones.list(): - print(zone.name) -``` - -```go -// Go -iter := client.Zones.ListAutoPaging(ctx, cloudflare.ZoneListParams{}) -for iter.Next() { - fmt.Println(iter.Current().Name) -} -``` - -## Error Handling with Retry - -**Problem:** Rate limits (429) and transient errors need retry. - -**Solution:** SDKs auto-retry with exponential backoff. Customize as needed. - -```typescript -// Increase retries for rate-limit-heavy operations -const client = new Cloudflare({ maxRetries: 5 }); - -try { - const zone = await client.zones.create({ /* ... */ }); -} catch (err) { - if (err instanceof Cloudflare.RateLimitError) { - // Already retried 5 times with backoff - const retryAfter = err.headers['retry-after']; - console.log(`Rate limited. Retry after ${retryAfter}s`); - } -} -``` - -## Batch Parallel Operations - -**Problem:** Need to create multiple resources quickly. - -**Solution:** Use `Promise.all()` for parallel requests (respect rate limits). - -```typescript -// Create multiple DNS records in parallel -const records = ['www', 'api', 'cdn'].map(subdomain => - client.dns.records.create({ - zone_id: 'zone-id', - type: 'A', - name: `${subdomain}.example.com`, - content: '192.0.2.1', - }) -); -await Promise.all(records); -``` - -**Controlled concurrency** (avoid rate limits): - -```typescript -import pLimit from 'p-limit'; -const limit = pLimit(10); // Max 10 concurrent - -const subdomains = ['www', 'api', 'cdn', /* many more */]; -const records = subdomains.map(subdomain => - limit(() => client.dns.records.create({ - zone_id: 'zone-id', - type: 'A', - name: `${subdomain}.example.com`, - content: '192.0.2.1', - })) -); -await Promise.all(records); -``` - -## Zone CRUD Workflow - -```typescript -// Create -const zone = await client.zones.create({ - account: { id: 'account-id' }, - name: 'example.com', - type: 'full', -}); - -// Read -const fetched = await client.zones.get({ zone_id: zone.id }); - -// Update -await client.zones.edit(zone.id, { paused: false }); - -// Delete -await client.zones.delete(zone.id); -``` - -## DNS Bulk Update - -```typescript -// Fetch all A records -const records = []; -for await (const record of client.dns.records.list({ - zone_id: 'zone-id', - type: 'A', -})) { - records.push(record); -} - -// Update all to new IP -await Promise.all(records.map(record => - client.dns.records.update({ - zone_id: 'zone-id', - dns_record_id: record.id, - type: 'A', - name: record.name, - content: '203.0.113.1', // New IP - proxied: record.proxied, - ttl: record.ttl, - }) -)); -``` - -## Filter and Collect Results - -```typescript -// Find all proxied A records -const proxiedRecords = []; -for await (const record of client.dns.records.list({ - zone_id: 'zone-id', - type: 'A', -})) { - if (record.proxied) { - proxiedRecords.push(record); - } -} -``` - -## Error Recovery Pattern - -```typescript -async function createZoneWithRetry(name: string, maxAttempts = 3) { - for (let attempt = 1; attempt <= maxAttempts; attempt++) { - try { - return await client.zones.create({ - account: { id: 'account-id' }, - name, - type: 'full', - }); - } catch (err) { - if (err instanceof Cloudflare.RateLimitError && attempt < maxAttempts) { - const retryAfter = parseInt(err.headers['retry-after'] || '5'); - console.log(`Rate limited, waiting ${retryAfter}s (retry ${attempt}/${maxAttempts})`); - await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); - } else { - throw err; - } - } - } -} -``` - -## Conditional Update Pattern - -```typescript -// Only update if zone is active -const zone = await client.zones.get({ zone_id: 'zone-id' }); -if (zone.status === 'active') { - await client.zones.edit(zone.id, { paused: false }); -} -``` - -## Batch with Error Handling - -```typescript -// Process multiple zones, continue on errors -const results = await Promise.allSettled( - zoneIds.map(id => client.zones.get({ zone_id: id })) -); - -results.forEach((result, i) => { - if (result.status === 'fulfilled') { - console.log(`Zone ${i}: ${result.value.name}`); - } else { - console.error(`Zone ${i} failed:`, result.reason.message); - } -}); -``` - -## See Also - -- [api.md](./api.md) - SDK client initialization, basic operations -- [gotchas.md](./gotchas.md) - Rate limits, common errors -- [configuration.md](./configuration.md) - SDK configuration options diff --git a/external/develop/devops/cloudflare/references/argo-smart-routing/README.md b/external/develop/devops/cloudflare/references/argo-smart-routing/README.md deleted file mode 100644 index ef5539d..0000000 --- a/external/develop/devops/cloudflare/references/argo-smart-routing/README.md +++ /dev/null @@ -1,90 +0,0 @@ -# Cloudflare Argo Smart Routing Skill Reference - -## Overview - -Cloudflare Argo Smart Routing is a performance optimization service that detects real-time network issues and routes web traffic across the most efficient network path. It continuously monitors network conditions and intelligently routes traffic through the fastest, most reliable routes in Cloudflare's network. - -**Note on Smart Shield:** Argo Smart Routing is being integrated into Cloudflare's Smart Shield product for enhanced DDoS protection and performance. Existing Argo customers maintain full functionality with gradual migration to Smart Shield features. - -## Quick Start - -### Enable via cURL -```bash -curl -X PATCH "https://api.cloudflare.com/client/v4/zones/{zone_id}/argo/smart_routing" \ - -H "Authorization: Bearer YOUR_API_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"value": "on"}' -``` - -### Enable via TypeScript SDK -```typescript -import Cloudflare from 'cloudflare'; - -const client = new Cloudflare({ apiToken: process.env.CLOUDFLARE_API_TOKEN }); - -const result = await client.argo.smartRouting.edit({ - zone_id: 'your-zone-id', - value: 'on', -}); - -console.log(`Argo enabled: ${result.value}`); -``` - -## Core Concepts - -### What It Does -- **Intelligent routing**: Detects congestion, outages, packet loss in real-time -- **Global optimization**: Routes across 300+ Cloudflare data centers -- **Automatic failover**: Switches paths when issues detected (typically <1s) -- **Works with existing setup**: No origin changes required - -### Billing Model -- Usage-based: Charged per GB of traffic (excluding DDoS/WAF mitigated traffic) -- Requires billing configuration before enabling -- Available on Enterprise+ plans (check zone eligibility) - -### When to Use -- **High-traffic production sites** with global user base -- **Latency-sensitive applications** (APIs, real-time services) -- **Sites behind Cloudflare proxy** (orange-clouded DNS records) -- **Combined with Tiered Cache** for maximum performance gains - -### When NOT to Use -- Development/staging environments (cost control) -- Low-traffic sites (<1TB/month) where cost may exceed benefit -- Sites with primarily single-region traffic - -## Should I Enable Argo? - -| Your Situation | Recommendation | -|----------------|----------------| -| Global production app, >1TB/month traffic | ✅ Enable - likely ROI positive | -| Enterprise plan, latency-critical APIs | ✅ Enable - performance matters | -| Regional site, <100GB/month traffic | ⚠️ Evaluate - cost may not justify | -| Development/staging environment | ❌ Disable - use in production only | -| Not yet configured billing | ❌ Configure billing first | - -## Reading Order by Task - -| Your Goal | Start With | Then Read | -|-----------|------------|-----------| -| Enable Argo for first time | Quick Start above → [configuration.md](configuration.md) | [gotchas.md](gotchas.md) | -| Use TypeScript/Python SDK | [api.md](api.md) | [patterns.md](patterns.md) | -| Terraform/IaC setup | [configuration.md](configuration.md) | - | -| Enable for Spectrum TCP app | [patterns.md](patterns.md) → Spectrum section | [api.md](api.md) | -| Troubleshoot enablement issue | [gotchas.md](gotchas.md) | [api.md](api.md) | -| Manage billing/usage | [patterns.md](patterns.md) → Billing section | [gotchas.md](gotchas.md) | - -## In This Reference - -- **[api.md](api.md)** - API endpoints, SDK methods, error handling, Python/TypeScript examples -- **[configuration.md](configuration.md)** - Terraform setup, environment config, billing configuration -- **[patterns.md](patterns.md)** - Tiered Cache integration, Spectrum TCP apps, billing management, validation patterns -- **[gotchas.md](gotchas.md)** - Common errors, permission issues, limits, best practices - -## See Also - -- [Cloudflare Argo Smart Routing Docs](https://developers.cloudflare.com/argo-smart-routing/) -- [Cloudflare Smart Shield](https://developers.cloudflare.com/smart-shield/) -- [Spectrum Documentation](https://developers.cloudflare.com/spectrum/) -- [Tiered Cache](https://developers.cloudflare.com/cache/how-to/tiered-cache/) diff --git a/external/develop/devops/cloudflare/references/argo-smart-routing/api.md b/external/develop/devops/cloudflare/references/argo-smart-routing/api.md deleted file mode 100644 index 5cd8f5e..0000000 --- a/external/develop/devops/cloudflare/references/argo-smart-routing/api.md +++ /dev/null @@ -1,240 +0,0 @@ -## API Reference - -**Note on Smart Shield:** Argo Smart Routing is being integrated into Cloudflare's Smart Shield product. API endpoints remain stable; existing integrations continue to work without changes. - -### Base Endpoint -``` -https://api.cloudflare.com/client/v4 -``` - -### Authentication -Use API tokens with Zone:Argo Smart Routing:Edit permissions: - -```bash -# Headers required -X-Auth-Email: user@example.com -Authorization: Bearer YOUR_API_TOKEN -``` - -### Get Argo Smart Routing Status - -**Endpoint:** `GET /zones/{zone_id}/argo/smart_routing` - -**Description:** Retrieves current Argo Smart Routing enablement status. - -**cURL Example:** -```bash -curl -X GET "https://api.cloudflare.com/client/v4/zones/{zone_id}/argo/smart_routing" \ - -H "Authorization: Bearer YOUR_API_TOKEN" \ - -H "Content-Type: application/json" -``` - -**Response:** -```json -{ - "result": { - "id": "smart_routing", - "value": "on", - "editable": true, - "modified_on": "2024-01-11T12:00:00Z" - }, - "success": true, - "errors": [], - "messages": [] -} -``` - -**TypeScript SDK Example:** -```typescript -import Cloudflare from 'cloudflare'; - -const client = new Cloudflare({ - apiToken: process.env.CLOUDFLARE_API_TOKEN -}); - -const status = await client.argo.smartRouting.get({ zone_id: 'your-zone-id' }); -console.log(`Argo status: ${status.value}, editable: ${status.editable}`); -``` - -**Python SDK Example:** -```python -from cloudflare import Cloudflare - -client = Cloudflare(api_token=os.environ.get('CLOUDFLARE_API_TOKEN')) - -status = client.argo.smart_routing.get(zone_id='your-zone-id') -print(f"Argo status: {status.value}, editable: {status.editable}") -``` - -### Update Argo Smart Routing Status - -**Endpoint:** `PATCH /zones/{zone_id}/argo/smart_routing` - -**Description:** Enable or disable Argo Smart Routing for a zone. - -**Request Body:** -```json -{ - "value": "on" // or "off" -} -``` - -**cURL Example:** -```bash -curl -X PATCH "https://api.cloudflare.com/client/v4/zones/{zone_id}/argo/smart_routing" \ - -H "Authorization: Bearer YOUR_API_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"value": "on"}' -``` - -**TypeScript SDK Example:** -```typescript -const result = await client.argo.smartRouting.edit({ - zone_id: 'your-zone-id', - value: 'on', -}); -console.log(`Updated: ${result.value} at ${result.modified_on}`); -``` - -**Python SDK Example:** -```python -result = client.argo.smart_routing.edit( - zone_id='your-zone-id', - value='on' -) -print(f"Updated: {result.value} at {result.modified_on}") -``` - -## Checking Editability Before Updates - -**Critical:** Always check the `editable` field before attempting to enable/disable Argo. When `editable: false`, the zone has restrictions (billing not configured, insufficient permissions, or plan limitations). - -**Pattern:** -```typescript -async function safelyEnableArgo(client: Cloudflare, zoneId: string): Promise { - const status = await client.argo.smartRouting.get({ zone_id: zoneId }); - - if (!status.editable) { - console.error('Cannot modify Argo: editable=false (check billing/permissions)'); - return false; - } - - if (status.value === 'on') { - console.log('Argo already enabled'); - return true; - } - - await client.argo.smartRouting.edit({ zone_id: zoneId, value: 'on' }); - console.log('Argo enabled successfully'); - return true; -} -``` - -**Python Pattern:** -```python -def safely_enable_argo(client: Cloudflare, zone_id: str) -> bool: - status = client.argo.smart_routing.get(zone_id=zone_id) - - if not status.editable: - print('Cannot modify Argo: editable=false (check billing/permissions)') - return False - - if status.value == 'on': - print('Argo already enabled') - return True - - client.argo.smart_routing.edit(zone_id=zone_id, value='on') - print('Argo enabled successfully') - return True -``` - -## Error Handling - -The TypeScript SDK provides typed error classes for robust error handling: - -```typescript -import Cloudflare from 'cloudflare'; -import { APIError, APIConnectionError, RateLimitError } from 'cloudflare'; - -async function enableArgoWithErrorHandling(client: Cloudflare, zoneId: string) { - try { - const result = await client.argo.smartRouting.edit({ - zone_id: zoneId, - value: 'on', - }); - return result; - } catch (error) { - if (error instanceof RateLimitError) { - console.error('Rate limited. Retry after:', error.response?.headers.get('retry-after')); - // Implement exponential backoff - } else if (error instanceof APIError) { - console.error('API error:', error.status, error.message); - if (error.status === 403) { - console.error('Permission denied - check API token scopes'); - } else if (error.status === 400) { - console.error('Bad request - verify zone_id and payload'); - } - } else if (error instanceof APIConnectionError) { - console.error('Connection failed:', error.message); - // Retry with exponential backoff - } else { - console.error('Unexpected error:', error); - } - throw error; - } -} -``` - -**Python Error Handling:** -```python -from cloudflare import Cloudflare, APIError, RateLimitError - -def enable_argo_with_error_handling(client: Cloudflare, zone_id: str): - try: - result = client.argo.smart_routing.edit(zone_id=zone_id, value='on') - return result - except RateLimitError as e: - print(f"Rate limited. Retry after: {e.response.headers.get('retry-after')}") - raise - except APIError as e: - print(f"API error: {e.status} - {e.message}") - if e.status == 403: - print('Permission denied - check API token scopes') - elif e.status == 400: - print('Bad request - verify zone_id and payload') - raise - except Exception as e: - print(f"Unexpected error: {e}") - raise -``` - -## Response Schema - -All Argo Smart Routing API responses follow this structure: - -```typescript -interface ArgoSmartRoutingResponse { - result: { - id: 'smart_routing'; - value: 'on' | 'off'; - editable: boolean; - modified_on: string; // ISO 8601 timestamp - }; - success: boolean; - errors: Array<{ - code: number; - message: string; - }>; - messages: Array; -} -``` - -## Key Response Fields - -| Field | Type | Description | -|-------|------|-------------| -| `value` | `"on" \| "off"` | Current enablement status | -| `editable` | `boolean` | Whether changes are allowed (check before PATCH) | -| `modified_on` | `string` | ISO timestamp of last modification | -| `success` | `boolean` | Whether request succeeded | -| `errors` | `Array` | Error details if `success: false` \ No newline at end of file diff --git a/external/develop/devops/cloudflare/references/argo-smart-routing/configuration.md b/external/develop/devops/cloudflare/references/argo-smart-routing/configuration.md deleted file mode 100644 index ba94d38..0000000 --- a/external/develop/devops/cloudflare/references/argo-smart-routing/configuration.md +++ /dev/null @@ -1,197 +0,0 @@ -## Configuration Management - -**Note on Smart Shield Evolution:** Argo Smart Routing is being integrated into Smart Shield. Configuration methods below remain valid; Terraform and IaC patterns unchanged. - -### Infrastructure as Code (Terraform) - -```hcl -# terraform/argo.tf -# Note: Use Cloudflare Terraform provider - -resource "cloudflare_argo" "example" { - zone_id = var.zone_id - smart_routing = "on" - tiered_caching = "on" -} - -variable "zone_id" { - description = "Cloudflare Zone ID" - type = string -} - -output "argo_enabled" { - value = cloudflare_argo.example.smart_routing - description = "Argo Smart Routing status" -} -``` - -### Environment-Based Configuration - -```typescript -// config/argo.ts -interface ArgoEnvironmentConfig { - enabled: boolean; - tieredCache: boolean; - monitoring: { - usageAlerts: boolean; - threshold: number; - }; -} - -const configs: Record = { - production: { - enabled: true, - tieredCache: true, - monitoring: { - usageAlerts: true, - threshold: 1000, // GB - }, - }, - staging: { - enabled: true, - tieredCache: false, - monitoring: { - usageAlerts: false, - threshold: 100, // GB - }, - }, - development: { - enabled: false, - tieredCache: false, - monitoring: { - usageAlerts: false, - threshold: 0, - }, - }, -}; - -export function getArgoConfig(env: string): ArgoEnvironmentConfig { - return configs[env] || configs.development; -} -``` - -### Pulumi Configuration - -```typescript -// pulumi/argo.ts -import * as cloudflare from '@pulumi/cloudflare'; - -const zone = new cloudflare.Zone('example-zone', { - zone: 'example.com', - plan: 'enterprise', -}); - -const argoSettings = new cloudflare.Argo('argo-config', { - zoneId: zone.id, - smartRouting: 'on', - tieredCaching: 'on', -}); - -export const argoEnabled = argoSettings.smartRouting; -export const zoneId = zone.id; -``` - -## Billing Configuration - -Before enabling Argo Smart Routing, ensure billing is configured for the account: - -**Prerequisites:** -1. Valid payment method on file -2. Enterprise or higher plan -3. Zone must have billing enabled - -**Check Billing Status via Dashboard:** -1. Navigate to Account → Billing -2. Verify payment method configured -3. Check zone subscription status - -**Note:** Attempting to enable Argo without billing configured will result in `editable: false` in API responses. - -## Environment Variable Setup - -**Required Environment Variables:** -```bash -# .env -CLOUDFLARE_API_TOKEN=your_api_token_here -CLOUDFLARE_ZONE_ID=your_zone_id_here -CLOUDFLARE_ACCOUNT_ID=your_account_id_here - -# Optional -ARGO_ENABLED=true -ARGO_TIERED_CACHE=true -``` - -**TypeScript Configuration Loader:** -```typescript -// config/env.ts -import { z } from 'zod'; - -const envSchema = z.object({ - CLOUDFLARE_API_TOKEN: z.string().min(1), - CLOUDFLARE_ZONE_ID: z.string().min(1), - CLOUDFLARE_ACCOUNT_ID: z.string().min(1), - ARGO_ENABLED: z.string().optional().default('false'), - ARGO_TIERED_CACHE: z.string().optional().default('false'), -}); - -export const env = envSchema.parse(process.env); - -export const argoConfig = { - enabled: env.ARGO_ENABLED === 'true', - tieredCache: env.ARGO_TIERED_CACHE === 'true', -}; -``` - -## CI/CD Integration - -**GitHub Actions Example:** -```yaml -# .github/workflows/deploy-argo.yml -name: Deploy Argo Configuration - -on: - push: - branches: [main] - paths: - - 'terraform/argo.tf' - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v2 - - - name: Terraform Init - run: terraform init - working-directory: ./terraform - - - name: Terraform Apply - run: terraform apply -auto-approve - working-directory: ./terraform - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - TF_VAR_zone_id: ${{ secrets.CLOUDFLARE_ZONE_ID }} -``` - -## Enterprise Preview Program - -For early access to Argo Smart Routing features and Smart Shield integration: - -**Eligibility:** -- Enterprise plan customers -- Active Cloudflare support contract -- Production traffic >100GB/month - -**How to Join:** -1. Contact Cloudflare account team or support -2. Request Argo/Smart Shield preview access -3. Receive preview zone configuration - -**Preview Features:** -- Enhanced analytics and reporting -- Smart Shield DDoS integration -- Advanced routing policies -- Priority support for routing issues \ No newline at end of file diff --git a/external/develop/devops/cloudflare/references/argo-smart-routing/gotchas.md b/external/develop/devops/cloudflare/references/argo-smart-routing/gotchas.md deleted file mode 100644 index 8d011d3..0000000 --- a/external/develop/devops/cloudflare/references/argo-smart-routing/gotchas.md +++ /dev/null @@ -1,111 +0,0 @@ -## Best Practices Summary - -**Smart Shield Note:** Argo Smart Routing evolving into Smart Shield. Best practices below remain applicable; monitor Cloudflare changelog for Smart Shield updates. - -1. **Always check editability** before attempting to enable/disable Argo -2. **Set up billing notifications** to avoid unexpected costs -3. **Combine with Tiered Cache** for maximum performance benefit -4. **Use in production only** - disable for dev/staging to control costs -5. **Monitor analytics** - require 500+ requests in 48h for detailed metrics -6. **Handle errors gracefully** - check for billing, permissions, zone compatibility -7. **Test configuration changes** in staging before production -8. **Use TypeScript SDK** for type safety and better developer experience -9. **Implement retry logic** for API calls in production systems -10. **Document zone-specific settings** for team visibility - -## Common Errors - -### "Argo unavailable" - -**Problem:** API returns error "Argo Smart Routing is unavailable for this zone" - -**Cause:** Zone not eligible or billing not set up - -**Solution:** -1. Verify zone has Enterprise or higher plan -2. Check billing is configured in Account → Billing -3. Ensure payment method is valid and current -4. Contact Cloudflare support if eligibility unclear - -### "Cannot enable/disable" - -**Problem:** API call succeeds but status remains unchanged, or `editable: false` in GET response - -**Cause:** Insufficient permissions or zone restrictions - -**Solution:** -1. Check API token has `Zone:Argo Smart Routing:Edit` permission -2. Verify `editable: true` in GET response before attempting PATCH -3. If `editable: false`, check: - - Billing configured for account - - Zone plan includes Argo (Enterprise+) - - No active zone holds or suspensions - - API token has correct scopes - -### `editable: false` Error - -**Problem:** GET request returns `"editable": false`, preventing enable/disable - -**Cause:** Zone-level restrictions from billing, plan, or permissions - -**Solution Pattern:** -```typescript -const status = await client.argo.smartRouting.get({ zone_id: zoneId }); - -if (!status.editable) { - // Don't attempt to modify - will fail - console.error('Cannot modify Argo settings:'); - console.error('- Check billing is configured'); - console.error('- Verify zone has Enterprise+ plan'); - console.error('- Confirm API token has Edit permission'); - throw new Error('Argo is not editable for this zone'); -} - -// Safe to proceed with enable/disable -await client.argo.smartRouting.edit({ zone_id: zoneId, value: 'on' }); -``` - -### Rate Limiting - -**Problem:** `429 Too Many Requests` error from API - -**Cause:** Exceeded API rate limits (typically 1200 requests per 5 minutes) - -**Solution:** -```typescript -import { RateLimitError } from 'cloudflare'; - -try { - await client.argo.smartRouting.edit({ zone_id: zoneId, value: 'on' }); -} catch (error) { - if (error instanceof RateLimitError) { - const retryAfter = error.response?.headers.get('retry-after'); - console.log(`Rate limited. Retry after ${retryAfter} seconds`); - - // Implement exponential backoff - await new Promise(resolve => setTimeout(resolve, (retryAfter || 60) * 1000)); - // Retry request - } -} -``` - -## Limits - -| Resource/Limit | Value | Notes | -|----------------|-------|-------| -| Min requests for analytics | 500 in 48h | For detailed metrics via GraphQL | -| Zones supported | Enterprise+ | Check zone plan in dashboard | -| Billing requirement | Must be configured | Before enabling; verify payment method | -| API rate limit | 1200 req / 5 min | Per API token across all endpoints | -| Spectrum apps | No hard limit | Each app can enable Argo independently | -| Traffic counting | Proxied only | Only orange-clouded DNS records count | -| DDoS/WAF exemption | Yes | Mitigated traffic excluded from billing | -| Analytics latency | 1-5 minutes | Real-time metrics not available | - -## Additional Resources - -- [Official Argo Smart Routing Docs](https://developers.cloudflare.com/argo-smart-routing/) -- [Cloudflare Smart Shield](https://developers.cloudflare.com/smart-shield/) -- [API Authentication](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) -- [Cloudflare TypeScript SDK](https://github.com/cloudflare/cloudflare-typescript) -- [Cloudflare Python SDK](https://github.com/cloudflare/cloudflare-python) diff --git a/external/develop/devops/cloudflare/references/argo-smart-routing/patterns.md b/external/develop/devops/cloudflare/references/argo-smart-routing/patterns.md deleted file mode 100644 index 1173b2d..0000000 --- a/external/develop/devops/cloudflare/references/argo-smart-routing/patterns.md +++ /dev/null @@ -1,104 +0,0 @@ -# Integration Patterns - -## Enable Argo + Tiered Cache - -```typescript -async function enableOptimalPerformance(client: Cloudflare, zoneId: string) { - await Promise.all([ - client.argo.smartRouting.edit({ zone_id: zoneId, value: 'on' }), - client.argo.tieredCaching.edit({ zone_id: zoneId, value: 'on' }), - ]); -} -``` - -**Flow:** Visitor → Edge (Lower-Tier) → [Cache Miss] → Upper-Tier → [Cache Miss + Argo] → Origin - -**Impact:** Argo ~30% latency reduction + Tiered Cache 50-80% origin offload - -## Usage Analytics (GraphQL) - -```graphql -query ArgoAnalytics($zoneTag: string!) { - viewer { - zones(filter: { zoneTag: $zoneTag }) { - httpRequestsAdaptiveGroups(limit: 1000) { - sum { argoBytes, bytes } - } - } - } -} -``` - -**Billing:** ~$0.10/GB. DDoS-mitigated and WAF-blocked traffic NOT charged. - -## Spectrum TCP Integration - -Enable Argo for non-HTTP traffic (databases, game servers, IoT): - -```typescript -// Update existing app -await client.spectrum.apps.update(appId, { zone_id: zoneId, argo_smart_routing: true }); - -// Create new app with Argo -await client.spectrum.apps.create({ - zone_id: zoneId, - dns: { type: 'CNAME', name: 'tcp.example.com' }, - origin_direct: ['tcp://origin.example.com:3306'], - protocol: 'tcp/3306', - argo_smart_routing: true, -}); -``` - -**Use cases:** MySQL/PostgreSQL (3306/5432), game servers, MQTT (1883), SSH (22) - -## Pre-Flight Validation - -```typescript -async function validateArgoEligibility(client: Cloudflare, zoneId: string) { - const status = await client.argo.smartRouting.get({ zone_id: zoneId }); - const zone = await client.zones.get({ zone_id: zoneId }); - - const issues: string[] = []; - if (!status.editable) issues.push('Zone not editable'); - if (['free', 'pro'].includes(zone.plan.legacy_id)) issues.push('Requires Business+ plan'); - if (zone.status !== 'active') issues.push('Zone not active'); - - return { canEnable: issues.length === 0, issues }; -} -``` - -## Post-Enable Verification - -```typescript -async function verifyArgoEnabled(client: Cloudflare, zoneId: string): Promise { - await new Promise(r => setTimeout(r, 2000)); // Wait for propagation - const status = await client.argo.smartRouting.get({ zone_id: zoneId }); - return status.value === 'on'; -} -``` - -## Full Setup Pattern - -```typescript -async function setupArgo(client: Cloudflare, zoneId: string) { - // 1. Validate - const { canEnable, issues } = await validateArgoEligibility(client, zoneId); - if (!canEnable) throw new Error(issues.join(', ')); - - // 2. Enable both features - await Promise.all([ - client.argo.smartRouting.edit({ zone_id: zoneId, value: 'on' }), - client.argo.tieredCaching.edit({ zone_id: zoneId, value: 'on' }), - ]); - - // 3. Verify - const [argo, cache] = await Promise.all([ - client.argo.smartRouting.get({ zone_id: zoneId }), - client.argo.tieredCaching.get({ zone_id: zoneId }), - ]); - - return { argo: argo.value === 'on', tieredCache: cache.value === 'on' }; -} -``` - -**When to combine:** High-traffic sites (>1TB/mo), global users, cacheable content. diff --git a/external/develop/devops/cloudflare/references/artifacts/README.md b/external/develop/devops/cloudflare/references/artifacts/README.md deleted file mode 100644 index af8ba30..0000000 --- a/external/develop/devops/cloudflare/references/artifacts/README.md +++ /dev/null @@ -1,79 +0,0 @@ -# Cloudflare Artifacts - -Store versioned file trees behind a repo-style interface that works from Workers, the REST API, and Git-compatible tooling. - -## Overview - -Use **Artifacts** when the thing you need to store is a versioned filesystem tree rather than a single object, key, or SQL row. - -Typical Artifacts use cases: -- Git-style repositories -- Per-agent, per-session, or per-task repos -- Build outputs and deployment bundles -- Checkpoints and generated assets -- Shared file trees passed between developer tools and Workers - -Artifacts is a good fit when the same content needs to be addressable from **Workers**, the **REST API**, and **Git-compatible clients**. - -Artifacts is especially useful for agent and automation workflows where each unit of work should have its own isolated repo and token. - -**Prefer retrieval over memory** for current availability, authentication details, route shapes, limits, and pricing. Start at `https://developers.cloudflare.com/artifacts/`. - -## When to Use Artifacts - -| Need | Use | Why | -|------|-----|-----| -| Versioned file trees such as repos, build outputs, checkpoints, or generated assets | Artifacts | Artifacts stores and shares **versioned filesystem content** | -| A git-compatible workflow with `clone`, `fetch`, `pull`, or `push` | Artifacts | Artifacts exposes **git-over-HTTPS remotes** and repo-scoped tokens | -| The same artifact accessible from Workers, HTTP APIs, and developer tooling | Artifacts | Artifacts is available through a **Workers binding**, **REST API**, and **git-compatible interface** | -| Large files by object key, app config by key, or relational app data | R2, KV, or D1 | Use storage products directly when you need **objects, key-value entries, or SQL rows**, not versioned file trees | - -## Recommended Workflow - -- Create one repo per agent, session, user workspace, or task when work should stay isolated. -- Fork from a stable baseline when many repos need the same starter files or prompts. -- Use branches only when collaborators share the same lifecycle and need to work in one repo. -- Use namespaces to separate environments, teams, or high-rate workloads. - -## Quick Start - -**From a Worker:** - -```typescript -interface Env { - ARTIFACTS: Artifacts; -} - -const created = await env.ARTIFACTS.create("starter-repo"); -// created.remote -> git remote URL -// created.token -> initial repo token -``` - -**From the REST API:** - -Use the namespace-scoped Artifacts base URL plus a gateway JWT. For imports from existing HTTPS remotes, use the REST API rather than the Workers binding. - -## Reading Order - -| Task | Read | -|------|------| -| Decide whether Artifacts is the right product | README only | -| Create or manage repos from a Worker | README → configuration.md → api.md | -| Integrate Artifacts from an external system | README → api.md | -| Set up agent or sandbox workflows | README → configuration.md | -| Verify exact auth, routes, limits, or pricing | Live docs first: `https://developers.cloudflare.com/artifacts/` | - -## In This Reference - -- **[api.md](api.md)** - Workers binding methods, REST routes, token and repo operations -- **[configuration.md](configuration.md)** - Wrangler binding shape, Worker typing, REST configuration guidance - -## See Also - -- [Cloudflare Artifacts Docs](https://developers.cloudflare.com/artifacts/) -- [Artifacts Git Protocol Docs](https://developers.cloudflare.com/artifacts/api/git-protocol/) -- [ArtifactFS Docs](https://developers.cloudflare.com/artifacts/guides/artifact-fs/) -- [Cloudflare Workers Docs](https://developers.cloudflare.com/workers/) -- [Cloudflare Durable Objects Docs](https://developers.cloudflare.com/durable-objects/) -- [Cloudflare R2 Docs](https://developers.cloudflare.com/r2/) -- [Cloudflare D1 Docs](https://developers.cloudflare.com/d1/) diff --git a/external/develop/devops/cloudflare/references/artifacts/api.md b/external/develop/devops/cloudflare/references/artifacts/api.md deleted file mode 100644 index 0314a0b..0000000 --- a/external/develop/devops/cloudflare/references/artifacts/api.md +++ /dev/null @@ -1,128 +0,0 @@ -# Artifacts API Reference - -Use Artifacts through the **Workers binding**, the **REST control plane**, and **Git-compatible remotes**. - -**Prefer retrieval** for exact request and response details. Verify current behavior at `https://developers.cloudflare.com/artifacts/` before relying on specific auth flows, route details, or generated binding types. - -## Workers Binding - -Artifacts exposes a Worker binding on `env.ARTIFACTS`. - -### Namespace Methods - -| Method | Use For | -|--------|---------| -| `create(name, opts?)` | Create a repo and receive its initial remote and token | -| `get(name)` | Resolve a repo handle for repo-scoped operations | -| `list(opts?)` | List repos in a namespace | -| `delete(name)` | Delete a repo | - -```typescript -const created = await env.ARTIFACTS.create("starter-repo", { - description: "Repository for automation experiments", - setDefaultBranch: "main" -}); -const repo = await env.ARTIFACTS.get("starter-repo"); -const page = await env.ARTIFACTS.list({ limit: 10 }); -``` - -Use the REST API when you need to import a repo from another HTTPS remote. - -### Repo Handle Methods - -Use a repo handle returned by `get()` or `create()`. - -| Method | Use For | -|--------|---------| -| `info()` | Read repo metadata, including the remote URL | -| `createToken(scope?, ttl?)` | Mint a repo-scoped read or write token | -| `listTokens()` | Inspect active tokens | -| `validateToken(token)` | Check whether a token is still valid | -| `revokeToken(tokenOrId)` | Revoke a token by ID or value | -| `fork(name, opts?)` | Fork one repo into another | - -```typescript -const repo = await env.ARTIFACTS.get("starter-repo"); -if (!repo) throw new Error("Repo not found"); - -const info = await repo.info(); -const token = await repo.createToken("read", 3600); -const forked = await repo.fork("starter-repo-copy", { - defaultBranchOnly: true -}); -``` - -### Binding Notes - -- Current docs describe the runtime binding surface as `create`, `get`, `list`, `delete`, and repo-handle methods like `info`, `createToken`, and `fork`. -- Use `npx wrangler types` in the target project and treat the generated `worker-configuration.d.ts` as the source of truth for that environment. -- If generated types appear to expose `import()` or a different `get()` shape, verify the live docs before depending on those methods. - -Verify current runtime behavior in the live docs before depending on methods that are not shown in the Workers binding reference. - -## REST API - -Artifacts currently documents a namespace-scoped control plane: - -```txt -https://artifacts.cloudflare.net/v1/api/namespaces/$ARTIFACTS_NAMESPACE -``` - -Some deployments also expose an `/edge/v1/api/...` base path. Verify the correct base URL for your environment in the live docs. - -Requests to the standard `/v1/api/...` routes use a **gateway JWT** with Bearer authentication. - -Returned repo tokens authenticate **Git operations** against the repo `remote`. They do not authenticate REST control-plane requests. - -Current docs show the standard Cloudflare v4 response envelope around REST results. - -### Repo Routes - -| Route | Use For | -|-------|---------| -| `POST /repos` | Create a repo | -| `GET /repos` | List repos | -| `GET /repos/:name` | Read repo metadata and remote | -| `DELETE /repos/:name` | Delete a repo | -| `POST /repos/:name/fork` | Fork a repo | -| `POST /repos/:name/import` | Import a public HTTPS remote | - -```bash -curl --request POST "$ARTIFACTS_BASE_URL/repos" \ - --header "Authorization: Bearer $ARTIFACTS_JWT" \ - --header "Content-Type: application/json" \ - --data '{"name":"starter-repo"}' -``` - -Important current details from the docs draft: -- `POST /repos/:name/import` accepts a full HTTPS remote URL such as GitHub or GitLab. -- Import supports options such as `branch`, `depth`, and `read_only`. -- Repo metadata includes fields such as description, default branch, timestamps, and the Git `remote`. - -### Token Routes - -| Route | Use For | -|-------|---------| -| `GET /repos/:name/tokens` | List repo tokens | -| `POST /tokens` | Create a token for a repo | -| `DELETE /tokens/:id` | Revoke a token by ID | - -Current docs show list-token filtering and pagination by token state. Retrieve the exact query shape from the live docs when you need token audit or cleanup workflows. - -Use **read** tokens for clone, fetch, pull, and indexing workflows. Use **write** tokens only when a workflow must push or otherwise mutate a repo. - -## Git-Compatible Access - -Artifacts returns repo `remote` URLs that work with standard git-over-HTTPS tooling. - -Recommended current auth pattern for local workflows: - -```bash -git -c http.extraHeader="Authorization: Bearer $ARTIFACTS_TOKEN" clone "$ARTIFACTS_REMOTE" artifacts-clone -``` - -Use a self-contained Basic-auth remote only for short-lived commands that need credentials embedded in the URL. - -`read` tokens support `clone`, `fetch`, and `pull`. `git push` requires a `write` token. - -For large repos where startup time matters more than a full clone, Artifacts also documents **ArtifactFS**. Retrieve current details from `https://developers.cloudflare.com/artifacts/` when you need mount-style access. diff --git a/external/develop/devops/cloudflare/references/artifacts/configuration.md b/external/develop/devops/cloudflare/references/artifacts/configuration.md deleted file mode 100644 index be6a589..0000000 --- a/external/develop/devops/cloudflare/references/artifacts/configuration.md +++ /dev/null @@ -1,92 +0,0 @@ -# Artifacts Configuration - -## Worker Binding - -Configure the `artifacts` binding in your Wrangler config: - -```toml -[[artifacts]] -binding = "ARTIFACTS" -namespace = "default" -``` - -This exposes Artifacts on `env.ARTIFACTS` inside your Worker. - -If you authenticate with `wrangler login`, current docs say Wrangler requests `artifacts:write` by default. - -## TypeScript - -Regenerate Worker types after adding the binding: - -```bash -npx wrangler types -``` - -Use the generated binding type in your environment definition: - -```typescript -interface Env { - ARTIFACTS: Artifacts; -} -``` - -Wrangler generates the `Artifacts` type from the binding. Treat the generated `worker-configuration.d.ts` file as the source of truth for your environment. - -## Structure Repos for Isolation - -Artifacts works best when autonomous work is isolated: -- Create one repo per agent, session, sandbox, or task when work should stay separate. -- Fork from a reviewed baseline instead of copying starter files into every new repo. -- Use branches only when collaborators share the same lifecycle and need to work in one repo. -- Use namespaces to separate environments, teams, or high-rate workloads. - -## REST Configuration - -For external systems, configure the namespace-scoped base URL and gateway JWT: - -```bash -export ARTIFACTS_NAMESPACE="default" -export ARTIFACTS_JWT="" -export ARTIFACTS_BASE_URL="https://artifacts.cloudflare.net/v1/api/namespaces/$ARTIFACTS_NAMESPACE" -``` - -Some environments also expose an `/edge/v1/api/...` base path. Verify the correct host and base path in the live docs for your Artifacts environment. - -Use environment variables or your secret manager. Do not hardcode gateway JWTs or repo tokens. - -## Repo Tokens - -Artifacts workflows usually involve repo-scoped tokens returned by `create()` or minted later through the binding or REST API. - -Keep the control plane and data plane separate: -- Use the **Workers binding** or **REST API** with a gateway JWT to create repos and mint tokens. -- Use repo-scoped tokens only for **Git operations** against the returned `remote`. - -Recommended handling: -- Mint the narrowest scope you need: `read` or `write` -- Prefer short-lived tokens for handoff between systems -- Revoke tokens that are no longer needed - -Verify the current token behavior and auth guidance in `https://developers.cloudflare.com/artifacts/` before building long-lived automation. - -## Git Consumers - -Artifacts is designed to work with standard git-over-HTTPS clients once you have a repo `remote` and an access token. - -Prefer header-based auth for local tooling so the full token stays out of the remote URL: - -```bash -git -c http.extraHeader="Authorization: Bearer $ARTIFACTS_TOKEN" clone "$ARTIFACTS_REMOTE" artifacts-clone -``` - -Use a Basic-auth remote only for short-lived commands that need a self-contained URL. - -## Retrieval Checklist - -Check the live docs before relying on: -- the current Workers binding surface -- exact token formats -- availability or product status -- route details for import, fork, and token-management flows -- the correct control-plane host or `/edge/v1` base path for your environment -- platform limits or pricing diff --git a/external/develop/devops/cloudflare/references/bindings/README.md b/external/develop/devops/cloudflare/references/bindings/README.md index 12d05ed..cf7a92c 100644 --- a/external/develop/devops/cloudflare/references/bindings/README.md +++ b/external/develop/devops/cloudflare/references/bindings/README.md @@ -6,8 +6,8 @@ Read the relevant current documentation before implementing. These references ro ## Start here -- [Bindings overview and catalog](https://developers.cloudflare.com/workers/runtime-apis/bindings/): capability model, available products, environment access, and binding lifecycle. -- [Storage options](https://developers.cloudflare.com/workers/platform/storage-options/): choose storage from consistency, query, and coordination requirements. +- [Bindings overview and catalog](https://developers.cloudflare.com/workers/runtime-apis/bindings/index.md): capability model, available products, environment access, and binding lifecycle. +- [Storage options](https://developers.cloudflare.com/workers/platform/storage-options/index.md): choose storage from consistency, query, and coordination requirements. - [api.md](./api.md): environment access, generated types, and product APIs. - [configuration.md](./configuration.md): binding configuration, environments, secrets, and local development. - [patterns.md](./patterns.md): Worker-to-Worker calls, testing, and resource selection. diff --git a/external/develop/devops/cloudflare/references/bindings/api.md b/external/develop/devops/cloudflare/references/bindings/api.md index 577fb46..4032849 100644 --- a/external/develop/devops/cloudflare/references/bindings/api.md +++ b/external/develop/devops/cloudflare/references/bindings/api.md @@ -4,16 +4,16 @@ Fetch the relevant documentation before choosing method signatures, binding type | Task | Current documentation | | --- | --- | -| Access bindings in handlers, entrypoint classes, or imported `env` | [Bindings and environment access](https://developers.cloudflare.com/workers/runtime-apis/bindings/) | -| Generate environment and runtime types; configure TypeScript or migrate from `@cloudflare/workers-types` | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/) | -| Use framework-specific environment access | [Hono on Workers](https://developers.cloudflare.com/workers/framework-guides/web-apps/more-web-frameworks/hono/) (follow the framework's linked documentation) | -| Read, write, delete, and list KV keys | [KV Workers API](https://developers.cloudflare.com/kv/api/) | -| Read, write, delete, and list R2 objects | [R2 Workers API](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/) | -| Prepare and bind SQL statements or execute batches | [D1 Workers Binding API](https://developers.cloudflare.com/d1/worker-api/) | -| Address Durable Objects and call their methods | [Durable Objects API](https://developers.cloudflare.com/durable-objects/api/) | -| Send queue messages | [Queues JavaScript APIs](https://developers.cloudflare.com/queues/configuration/javascript-apis/) | -| Run model inference | [Workers AI bindings](https://developers.cloudflare.com/workers-ai/configuration/bindings/) | -| Call another Worker using HTTP or typed RPC | [Service bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) and [RPC TypeScript](https://developers.cloudflare.com/workers/runtime-apis/rpc/typescript/) | -| Find other product binding APIs, including Browser, mTLS, rate limiting, and Workflows | [Current binding catalog](https://developers.cloudflare.com/workers/runtime-apis/bindings/) | +| Access bindings in handlers, entrypoint classes, or imported `env` | [Bindings and environment access](https://developers.cloudflare.com/workers/runtime-apis/bindings/index.md) | +| Generate environment and runtime types; configure TypeScript or migrate from `@cloudflare/workers-types` | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/index.md) | +| Use framework-specific environment access | [Hono on Workers](https://developers.cloudflare.com/workers/framework-guides/web-apps/more-web-frameworks/hono/index.md) (follow the framework's linked documentation) | +| Read, write, delete, and list KV keys | [KV Workers API](https://developers.cloudflare.com/kv/api/index.md) | +| Read, write, delete, and list R2 objects | [R2 Workers API](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/index.md) | +| Prepare and bind SQL statements or execute batches | [D1 Workers Binding API](https://developers.cloudflare.com/d1/worker-api/index.md) | +| Address Durable Objects and call their methods | [Durable Objects API](https://developers.cloudflare.com/durable-objects/api/index.md) | +| Send queue messages | [Queues JavaScript APIs](https://developers.cloudflare.com/queues/configuration/javascript-apis/index.md) | +| Run model inference | [Workers AI bindings](https://developers.cloudflare.com/workers-ai/configuration/bindings/index.md) | +| Call another Worker using HTTP or typed RPC | [Service bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/index.md) and [RPC TypeScript](https://developers.cloudflare.com/workers/runtime-apis/rpc/typescript/index.md) | +| Find other product binding APIs, including Browser, mTLS, rate limiting, and Workflows | [Current binding catalog](https://developers.cloudflare.com/workers/runtime-apis/bindings/index.md) | Regenerate types after configuration changes and use the selected environment's configuration. Follow the current TypeScript setup for the project's toolchain instead of hardcoding a generated declaration path or maintaining a handwritten binding interface. Types describe the expected bindings; they do not provision resources or prove the deployed environment is configured correctly. diff --git a/external/develop/devops/cloudflare/references/bindings/configuration.md b/external/develop/devops/cloudflare/references/bindings/configuration.md index d4bd7e7..e60a29f 100644 --- a/external/develop/devops/cloudflare/references/bindings/configuration.md +++ b/external/develop/devops/cloudflare/references/bindings/configuration.md @@ -4,16 +4,16 @@ Confirm the target account, environment, and resource before adding or changing | Task | Current documentation | | --- | --- | -| Configure storage, compute, platform, and service bindings | [Wrangler binding configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) | -| Create or inspect resources and manage deployments | [Wrangler commands](https://developers.cloudflare.com/workers/wrangler/commands/) (select the product's commands) | -| Configure named environments and non-inheritable bindings | [Wrangler environments](https://developers.cloudflare.com/workers/wrangler/environments/) | -| Set non-sensitive configuration | [Environment variables](https://developers.cloudflare.com/workers/configuration/environment-variables/) | -| Set or rotate credentials in a chosen environment | [Secrets](https://developers.cloudflare.com/workers/configuration/secrets/) | -| Configure text, data, and Wasm modules in existing projects | [Wrangler bundling](https://developers.cloudflare.com/workers/wrangler/bundling/) and [configuration](https://developers.cloudflare.com/workers/wrangler/configuration/) | -| Choose locally simulated resources or remote bindings | [Local development](https://developers.cloudflare.com/workers/local-development/) and [supported bindings per development mode](https://developers.cloudflare.com/workers/local-development/bindings-per-env/) | -| Supply local variables and secrets | [Local environment variables and secrets](https://developers.cloudflare.com/workers/local-development/environment-variables/) | -| Seed or persist local resource data | [Adding local data](https://developers.cloudflare.com/workers/local-development/local-data/) | -| Generate types after configuring bindings | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/) | +| Configure storage, compute, platform, and service bindings | [Wrangler binding configuration](https://developers.cloudflare.com/workers/wrangler/configuration/index.md#bindings) | +| Create or inspect resources and manage deployments | [Wrangler commands](https://developers.cloudflare.com/workers/wrangler/commands/index.md) (select the product's commands) | +| Configure named environments and non-inheritable bindings | [Wrangler environments](https://developers.cloudflare.com/workers/wrangler/environments/index.md) | +| Set non-sensitive configuration | [Environment variables](https://developers.cloudflare.com/workers/configuration/environment-variables/index.md) | +| Set or rotate credentials in a chosen environment | [Secrets](https://developers.cloudflare.com/workers/configuration/secrets/index.md) | +| Configure text, data, and Wasm modules in existing projects | [Wrangler bundling](https://developers.cloudflare.com/workers/wrangler/bundling/index.md) and [configuration](https://developers.cloudflare.com/workers/wrangler/configuration/index.md) | +| Choose locally simulated resources or remote bindings | [Local development](https://developers.cloudflare.com/workers/local-development/index.md) and [supported bindings per development mode](https://developers.cloudflare.com/workers/local-development/bindings-per-env/index.md) | +| Supply local variables and secrets | [Local environment variables and secrets](https://developers.cloudflare.com/workers/local-development/environment-variables/index.md) | +| Seed or persist local resource data | [Adding local data](https://developers.cloudflare.com/workers/local-development/local-data/index.md) | +| Generate types after configuring bindings | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/index.md) | Bindings and variables are not inherited automatically by named environments. Configure the required values for the environment being used, including its secrets. Binding names used in code must match configuration; the target resource's ID or name is a separate value. diff --git a/external/develop/devops/cloudflare/references/bindings/gotchas.md b/external/develop/devops/cloudflare/references/bindings/gotchas.md index b3e072d..bca53dc 100644 --- a/external/develop/devops/cloudflare/references/bindings/gotchas.md +++ b/external/develop/devops/cloudflare/references/bindings/gotchas.md @@ -4,15 +4,15 @@ Start by checking the binding name, selected environment, and actual target reso | Symptom or question | What to check | | --- | --- | -| A binding is undefined or points to unexpected data | [Binding configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) and [environment inheritance](https://developers.cloudflare.com/workers/wrangler/environments/) | -| TypeScript cannot find a binding or runtime type | [Current type generation and TypeScript setup](https://developers.cloudflare.com/workers/languages/typescript/) | -| Updated credentials are not reflected in a client | [Binding changes and retained global state](https://developers.cloudflare.com/workers/runtime-apis/bindings/#making-changes-to-bindings); verify the [secret's environment and deployment](https://developers.cloudflare.com/workers/configuration/secrets/) | -| Binding calls fail outside a handler | [Environment access and global-scope restrictions](https://developers.cloudflare.com/workers/runtime-apis/bindings/#how-to-access-env) | -| A service target is unavailable or incorrect | [Service bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) and [multi-Worker development](https://developers.cloudflare.com/workers/local-development/multi-workers/) | -| Local data, secrets, or available bindings differ from deployment | [Development mode support](https://developers.cloudflare.com/workers/local-development/bindings-per-env/), [local data](https://developers.cloudflare.com/workers/local-development/local-data/), and [local secrets](https://developers.cloudflare.com/workers/local-development/environment-variables/) | -| KV reads appear stale or return no value | [How KV works](https://developers.cloudflare.com/kv/concepts/how-kv-works/); verify the namespace and handle missing values | -| Resource-specific API errors or limits | Follow the product from the [binding catalog](https://developers.cloudflare.com/workers/runtime-apis/bindings/) to its troubleshooting and limits documentation | -| Worker resource limits or unexpected charges | [Workers limits](https://developers.cloudflare.com/workers/platform/limits/) and [pricing](https://developers.cloudflare.com/workers/platform/pricing/); check each bound product's limits and pricing separately | -| Need to inspect configuration, resources, deployments, or logs | [Wrangler commands](https://developers.cloudflare.com/workers/wrangler/commands/) and [real-time logs](https://developers.cloudflare.com/workers/observability/logs/real-time-logs/) | +| A binding is undefined or points to unexpected data | [Binding configuration](https://developers.cloudflare.com/workers/wrangler/configuration/index.md#bindings) and [environment inheritance](https://developers.cloudflare.com/workers/wrangler/environments/index.md) | +| TypeScript cannot find a binding or runtime type | [Current type generation and TypeScript setup](https://developers.cloudflare.com/workers/languages/typescript/index.md) | +| Updated credentials are not reflected in a client | [Binding changes and retained global state](https://developers.cloudflare.com/workers/runtime-apis/bindings/index.md#making-changes-to-bindings); verify the [secret's environment and deployment](https://developers.cloudflare.com/workers/configuration/secrets/index.md) | +| Binding calls fail outside a handler | [Environment access and global-scope restrictions](https://developers.cloudflare.com/workers/runtime-apis/bindings/index.md#how-to-access-env) | +| A service target is unavailable or incorrect | [Service bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/index.md) and [multi-Worker development](https://developers.cloudflare.com/workers/local-development/multi-workers/index.md) | +| Local data, secrets, or available bindings differ from deployment | [Development mode support](https://developers.cloudflare.com/workers/local-development/bindings-per-env/index.md), [local data](https://developers.cloudflare.com/workers/local-development/local-data/index.md), and [local secrets](https://developers.cloudflare.com/workers/local-development/environment-variables/index.md) | +| KV reads appear stale or return no value | [How KV works](https://developers.cloudflare.com/kv/concepts/how-kv-works/index.md); verify the namespace and handle missing values | +| Resource-specific API errors or limits | Follow the product from the [binding catalog](https://developers.cloudflare.com/workers/runtime-apis/bindings/index.md) to its troubleshooting and limits documentation | +| Worker resource limits or unexpected charges | [Workers limits](https://developers.cloudflare.com/workers/platform/limits/index.md) and [pricing](https://developers.cloudflare.com/workers/platform/pricing/index.md); check each bound product's limits and pricing separately | +| Need to inspect configuration, resources, deployments, or logs | [Wrangler commands](https://developers.cloudflare.com/workers/wrangler/commands/index.md) and [real-time logs](https://developers.cloudflare.com/workers/observability/logs/real-time-logs/index.md) | Never log secret values or return the environment object in a response. Inspect names and configuration without exposing credentials. Check the selected resource and development mode before issuing debugging commands that could mutate remote data. diff --git a/external/develop/devops/cloudflare/references/bindings/patterns.md b/external/develop/devops/cloudflare/references/bindings/patterns.md index a77f7d2..b6a0ebd 100644 --- a/external/develop/devops/cloudflare/references/bindings/patterns.md +++ b/external/develop/devops/cloudflare/references/bindings/patterns.md @@ -4,14 +4,14 @@ Choose the interaction and lifecycle first, then retrieve the implementation gui | Task | Current documentation | | --- | --- | -| Choose HTTP forwarding or RPC between Workers | [Service bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) | -| Forward Requests and Responses through a service binding | [Service bindings over HTTP](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/http/) | -| Expose callable methods with `WorkerEntrypoint` | [Service bindings over RPC](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/) and [RPC TypeScript](https://developers.cloudflare.com/workers/runtime-apis/rpc/typescript/) | -| Run connected Workers during development | [Developing with multiple Workers](https://developers.cloudflare.com/workers/local-development/multi-workers/) | -| Test handlers against configured bindings and mock dependencies | [Workers Vitest configuration](https://developers.cloudflare.com/workers/testing/vitest-integration/configuration/) and [test APIs](https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/) | -| Select KV, D1, R2, or Durable Objects | [Storage options](https://developers.cloudflare.com/workers/platform/storage-options/) | -| Keep clients current when bindings change | [Binding lifecycle](https://developers.cloudflare.com/workers/runtime-apis/bindings/#making-changes-to-bindings) | -| Manage credentials used by external API clients | [Secrets](https://developers.cloudflare.com/workers/configuration/secrets/) | +| Choose HTTP forwarding or RPC between Workers | [Service bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/index.md) | +| Forward Requests and Responses through a service binding | [Service bindings over HTTP](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/http/index.md) | +| Expose callable methods with `WorkerEntrypoint` | [Service bindings over RPC](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/index.md) and [RPC TypeScript](https://developers.cloudflare.com/workers/runtime-apis/rpc/typescript/index.md) | +| Run connected Workers during development | [Developing with multiple Workers](https://developers.cloudflare.com/workers/local-development/multi-workers/index.md) | +| Test handlers against configured bindings and mock dependencies | [Workers Vitest configuration](https://developers.cloudflare.com/workers/testing/vitest-integration/configuration/index.md) and [test APIs](https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/index.md) | +| Select KV, D1, R2, or Durable Objects | [Storage options](https://developers.cloudflare.com/workers/platform/storage-options/index.md) | +| Keep clients current when bindings change | [Binding lifecycle](https://developers.cloudflare.com/workers/runtime-apis/bindings/index.md#making-changes-to-bindings) | +| Manage credentials used by external API clients | [Secrets](https://developers.cloudflare.com/workers/configuration/secrets/index.md) | Use service bindings for internal Worker calls when appropriate, and choose HTTP or RPC based on the interface being exposed. A service binding does not replace application-level authorization for the caller's requested operation. diff --git a/external/develop/devops/cloudflare/references/bot-management/README.md b/external/develop/devops/cloudflare/references/bot-management/README.md deleted file mode 100644 index 034fb3b..0000000 --- a/external/develop/devops/cloudflare/references/bot-management/README.md +++ /dev/null @@ -1,94 +0,0 @@ -# Cloudflare Bot Management - -Enterprise-grade bot detection, protection, and mitigation using ML/heuristics, bot scores, JavaScript detections, and verified bot handling. - -## Overview - -Bot Management provides multi-tier protection: -- **Free (Bot Fight Mode)**: Auto-blocks definite bots, no config -- **Pro/Business (Super Bot Fight Mode)**: Configurable actions, static resource protection, analytics groupings -- **Enterprise (Bot Management)**: Granular 1-99 scores, WAF integration, JA3/JA4 fingerprinting, Workers API, Advanced Analytics - -## Quick Start - -```txt -# Dashboard: Security > Bots -# Enterprise: Deploy rule template -(cf.bot_management.score eq 1 and not cf.bot_management.verified_bot) → Block -(cf.bot_management.score le 29 and not cf.bot_management.verified_bot) → Managed Challenge -``` - -## What Do You Need? - -```txt -├─ Initial setup → configuration.md -│ ├─ Free tier → "Bot Fight Mode" -│ ├─ Pro/Business → "Super Bot Fight Mode" -│ └─ Enterprise → "Bot Management for Enterprise" -├─ Workers API integration → api.md -├─ WAF rules → patterns.md -├─ Debugging → gotchas.md -└─ Analytics → api.md#bot-analytics -``` - -## Reading Order - -| Task | Files to Read | -|------|---------------| -| Enable bot protection | README → configuration.md | -| Workers bot detection | README → api.md | -| WAF rule templates | README → patterns.md | -| Debug bot issues | gotchas.md | -| Advanced analytics | api.md#bot-analytics | - -## Core Concepts - -**Bot Scores**: 1-99 (1 = definitely automated, 99 = definitely human). Threshold: <30 indicates bot traffic. Enterprise gets granular 1-99; Pro/Business get groupings only. - -**Detection Engines**: Heuristics (known fingerprints, assigns score=1), ML (majority of detections, supervised learning on billions of requests), Anomaly Detection (optional, baseline traffic analysis), JavaScript Detections (headless browser detection). - -**Verified Bots**: Allowlisted good bots (search engines, AI crawlers) verified via reverse DNS or Web Bot Auth. Access via `cf.bot_management.verified_bot` or `cf.verified_bot_category`. - -## Platform Limits - -| Plan | Bot Scores | JA3/JA4 | Custom Rules | Analytics Retention | -|------|------------|---------|--------------|---------------------| -| Free | No (auto-block only) | No | 5 | N/A (no analytics) | -| Pro/Business | Groupings only | No | 20/100 | 30 days (72h at a time) | -| Enterprise | 1-99 granular | Yes | 1,000+ | 30 days (1 week at a time) | - -## Basic Patterns - -```typescript -// Workers: Check bot score -export default { - async fetch(request: Request): Promise { - const botScore = request.cf?.botManagement?.score; - if (botScore && botScore < 30 && !request.cf?.botManagement?.verifiedBot) { - return new Response('Bot detected', { status: 403 }); - } - return fetch(request); - } -}; -``` - -```txt -# WAF: Block definite bots -(cf.bot_management.score eq 1 and not cf.bot_management.verified_bot) - -# WAF: Protect sensitive endpoints -(cf.bot_management.score lt 50 and http.request.uri.path in {"/login" "/checkout"} and not cf.bot_management.verified_bot) -``` - -## In This Reference - -- [configuration.md](./configuration.md) - Product tiers, WAF rule setup, JavaScript Detections, ML auto-updates -- [api.md](./api.md) - Workers BotManagement interface, WAF fields, JA4 Signals -- [patterns.md](./patterns.md) - E-commerce, API protection, mobile app allowlisting, SEO-friendly handling -- [gotchas.md](./gotchas.md) - False positives/negatives, score=0 issues, JSD limitations, CSP requirements - -## See Also - -- [waf](../waf/) - WAF custom rules for bot enforcement -- [workers](https://developers.cloudflare.com/workers/) - Workers request.cf.botManagement API -- [api-shield](../api-shield/) - API-specific bot protection diff --git a/external/develop/devops/cloudflare/references/bot-management/api.md b/external/develop/devops/cloudflare/references/bot-management/api.md deleted file mode 100644 index eda958a..0000000 --- a/external/develop/devops/cloudflare/references/bot-management/api.md +++ /dev/null @@ -1,169 +0,0 @@ -# Bot Management API - -## Workers: BotManagement Interface - -```typescript -interface BotManagement { - score: number; // 1-99 (Enterprise), 0 if not computed - verifiedBot: boolean; // Is verified bot - staticResource: boolean; // Serves static resource - ja3Hash: string; // JA3 fingerprint (Enterprise, HTTPS only) - ja4: string; // JA4 fingerprint (Enterprise, HTTPS only) - jsDetection?: { - passed: boolean; // Passed JS detection (if enabled) - }; - detectionIds: number[]; // Heuristic detection IDs - corporateProxy?: boolean; // From corporate proxy (Enterprise) -} - -// DEPRECATED: Use botManagement.score instead -// request.cf.clientTrustScore (legacy, duplicate of botManagement.score) - -// Access via request.cf -import type { IncomingRequestCfProperties } from '@cloudflare/workers-types'; - -export default { - async fetch(request: Request): Promise { - const cf = request.cf as IncomingRequestCfProperties | undefined; - const botMgmt = cf?.botManagement; - - if (!botMgmt) return fetch(request); - if (botMgmt.verifiedBot) return fetch(request); // Allow verified bots - if (botMgmt.score === 1) return new Response('Blocked', { status: 403 }); - if (botMgmt.score < 30) return new Response('Challenge required', { status: 429 }); - - return fetch(request); - } -}; -``` - -## WAF Fields Reference - -```txt -# Score fields -cf.bot_management.score # 0-99 (0 = not computed) -cf.bot_management.verified_bot # boolean -cf.bot_management.static_resource # boolean -cf.bot_management.ja3_hash # string (Enterprise) -cf.bot_management.ja4 # string (Enterprise) -cf.bot_management.detection_ids # array -cf.bot_management.js_detection.passed # boolean -cf.bot_management.corporate_proxy # boolean (Enterprise) -cf.verified_bot_category # string - -# Workers equivalent -request.cf.botManagement.score -request.cf.botManagement.verifiedBot -request.cf.botManagement.ja3Hash -request.cf.botManagement.ja4 -request.cf.botManagement.jsDetection.passed -request.cf.verifiedBotCategory -``` - -## JA4 Signals (Enterprise) - -```typescript -import type { IncomingRequestCfProperties } from '@cloudflare/workers-types'; - -interface JA4Signals { - // Ratios (0.0-1.0) - heuristic_ratio_1h?: number; // Fraction flagged by heuristics - browser_ratio_1h?: number; // Fraction from real browsers - cache_ratio_1h?: number; // Fraction hitting cache - h2h3_ratio_1h?: number; // Fraction using HTTP/2 or HTTP/3 - // Ranks (relative position in distribution) - uas_rank_1h?: number; // User-Agent diversity rank - paths_rank_1h?: number; // Path diversity rank - reqs_rank_1h?: number; // Request volume rank - ips_rank_1h?: number; // IP diversity rank - // Quantiles (0.0-1.0, percentile in distribution) - reqs_quantile_1h?: number; // Request volume quantile - ips_quantile_1h?: number; // IP count quantile -} - -export default { - async fetch(request: Request): Promise { - const cf = request.cf as IncomingRequestCfProperties | undefined; - const ja4Signals = cf?.ja4Signals as JA4Signals | undefined; - - if (!ja4Signals) return fetch(request); // Not available for HTTP or Worker routing - - // Check for anomalous behavior - // High heuristic_ratio or low browser_ratio = suspicious - const heuristicRatio = ja4Signals.heuristic_ratio_1h ?? 0; - const browserRatio = ja4Signals.browser_ratio_1h ?? 0; - - if (heuristicRatio > 0.5 || browserRatio < 0.3) { - return new Response('Suspicious traffic', { status: 403 }); - } - - return fetch(request); - } -}; -``` - -## Common Patterns - -See [patterns.md](./patterns.md) for Workers examples: mobile app allowlisting, corporate proxy exemption, datacenter detection, conditional delay, and more. - -## Bot Analytics - -### Access Locations -- Dashboard: Security > Bots (old) or Security > Analytics > Bot analysis (new) -- GraphQL API for programmatic access -- Security Events & Security Analytics -- Logpush/Logpull - -### Available Data -- **Enterprise BM**: Bot scores (1-99), bot score source, distribution -- **Pro/Business**: Bot groupings (automated, likely automated, likely human) -- Top attributes: IPs, paths, user agents, countries -- Detection sources: Heuristics, ML, AD, JSD -- Verified bot categories - -### Time Ranges -- **Enterprise BM**: Up to 1 week at a time, 30 days history -- **Pro/Business**: Up to 72 hours at a time, 30 days history -- Real-time in most cases, adaptive sampling (1-10% depending on volume) - -## Logpush Fields - -```txt -BotScore # 1-99 or 0 if not computed -BotScoreSrc # Detection engine (ML, Heuristics, etc.) -BotTags # Classification tags -BotDetectionIDs # Heuristic detection IDs -``` - -**BotScoreSrc values:** -- `"Heuristics"` - Known fingerprint -- `"Machine Learning"` - ML model -- `"Anomaly Detection"` - Baseline anomaly -- `"JS Detection"` - JavaScript check -- `"Cloudflare Service"` - Zero Trust -- `"Not Computed"` - Score = 0 - -Access via Logpush (stream to cloud storage/SIEM), Logpull (API to fetch logs), or GraphQL API (query analytics data). - -## Testing with Miniflare - -Miniflare provides mock botManagement data for local development: - -**Default values:** -- `score: 99` (human) -- `verifiedBot: false` -- `corporateProxy: false` -- `ja3Hash: "25b4882c2bcb50cd6b469ff28c596742"` -- `staticResource: false` -- `detectionIds: []` - -**Override in tests:** -```typescript -import { getPlatformProxy } from 'wrangler'; - -const { cf, dispose } = await getPlatformProxy(); -// cf.botManagement is frozen mock object -expect(cf.botManagement.score).toBe(99); -``` - -For custom test data, mock request.cf in your test setup. diff --git a/external/develop/devops/cloudflare/references/bot-management/configuration.md b/external/develop/devops/cloudflare/references/bot-management/configuration.md deleted file mode 100644 index 4166bcc..0000000 --- a/external/develop/devops/cloudflare/references/bot-management/configuration.md +++ /dev/null @@ -1,163 +0,0 @@ -# Bot Management Configuration - -## Product Tiers - -**Note:** Dashboard paths differ between old and new UI: -- **New:** Security > Settings > Filter "Bot traffic" -- **Old:** Security > Bots - -Both UIs access same settings. - -### Bot Score Groupings (Pro/Business) - -Pro/Business users see bot score groupings instead of granular 1-99 scores: - -| Score | Grouping | Meaning | -|-------|----------|---------| -| 0 | Not computed | Bot Management didn't run | -| 1 | Automated | Definite bot (heuristic match) | -| 2-29 | Likely automated | Probably bot (ML detection) | -| 30-99 | Likely human | Probably human | -| N/A | Verified bot | Allowlisted good bot | - -Enterprise plans get granular 1-99 scores for custom thresholds. - -### Bot Fight Mode (Free) -- Auto-blocks definite bots (score=1), excludes verified bots by default -- JavaScript Detections always enabled, no configuration options - -### Super Bot Fight Mode (Pro/Business) -```txt -Dashboard: Security > Bots > Configure -- Definitely automated: Block/Challenge -- Likely automated: Challenge/Allow -- Verified bots: Allow (recommended) -- Static resource protection: ON (may block mail clients) -- JavaScript Detections: Optional -``` - -### Bot Management for Enterprise -```txt -Dashboard: Security > Bots > Configure > Auto-updates: ON (recommended) - -# Template 1: Block definite bots -(cf.bot_management.score eq 1 and not cf.bot_management.verified_bot and not cf.bot_management.static_resource) -Action: Block - -# Template 2: Challenge likely bots -(cf.bot_management.score ge 2 and cf.bot_management.score le 29 and not cf.bot_management.verified_bot and not cf.bot_management.static_resource) -Action: Managed Challenge -``` - -## JavaScript Detections Setup - -### Enable via Dashboard -```txt -Security > Bots > Configure Bot Management > JS Detections: ON - -Update CSP: script-src 'self' /cdn-cgi/challenge-platform/; -``` - -### Manual JS Injection (API) -```html - - -``` - -**Use API for**: Selective deployment on specific pages -**Don't combine**: Zone-wide toggle + manual injection - -### WAF Rules for JSD -```txt -# NEVER use on first page visit (needs HTML page first) -(not cf.bot_management.js_detection.passed and http.request.uri.path eq "/api/user/create" and http.request.method eq "POST" and not cf.bot_management.verified_bot) -Action: Managed Challenge (always use Managed Challenge, not Block) -``` - -### Limitations -- First request won't have JSD data (needs HTML page first) -- Strips ETags from HTML responses -- Not supported with CSP via `` tags -- Websocket endpoints not supported -- Native mobile apps won't pass -- cf_clearance cookie: 15-minute lifespan, max 4096 bytes - -## __cf_bm Cookie - -Cloudflare sets `__cf_bm` cookie to smooth bot scores across user sessions: - -- **Purpose:** Reduces false positives from score volatility -- **Scope:** Per-domain, HTTP-only -- **Lifespan:** Session duration -- **Privacy:** No PII—only session classification -- **Automatic:** No configuration required - -Bot scores for repeat visitors consider session history via this cookie. - -## Static Resource Protection - -**File Extensions**: ico, jpg, png, jpeg, gif, css, js, tif, tiff, bmp, pict, webp, svg, svgz, class, jar, txt, csv, doc, docx, xls, xlsx, pdf, ps, pls, ppt, pptx, ttf, otf, woff, woff2, eot, eps, ejs, swf, torrent, midi, mid, m3u8, m4a, mp3, ogg, ts -**Plus**: `/.well-known/` path (all files) - -```txt -# Exclude static resources from bot rules -(cf.bot_management.score lt 30 and not cf.bot_management.static_resource) -``` - -**WARNING**: May block mail clients fetching static images - -## JA3/JA4 Fingerprinting (Enterprise) - -```txt -# Block specific attack fingerprint -(cf.bot_management.ja3_hash eq "8b8e3d5e3e8b3d5e") - -# Allow mobile app by fingerprint -(cf.bot_management.ja4 eq "your_mobile_app_fingerprint") -``` - -Only available for HTTPS/TLS traffic. Missing for Worker-routed traffic or HTTP requests. - -## Verified Bot Categories - -```txt -# Allow search engines only -(cf.verified_bot_category eq "Search Engine Crawler") - -# Block AI crawlers -(cf.verified_bot_category eq "AI Crawler") -Action: Block - -# Or use dashboard: Security > Settings > Bot Management > Block AI Bots -``` - -| Category | String Value | Example | -|----------|--------------|---------| -| AI Crawler | `AI Crawler` | GPTBot, Claude-Web | -| AI Assistant | `AI Assistant` | Perplexity-User, DuckAssistBot | -| AI Search | `AI Search` | OAI-SearchBot | -| Accessibility | `Accessibility` | Accessible Web Bot | -| Academic Research | `Academic Research` | Library of Congress | -| Advertising & Marketing | `Advertising & Marketing` | Google Adsbot | -| Aggregator | `Aggregator` | Pinterest, Indeed | -| Archiver | `Archiver` | Internet Archive, CommonCrawl | -| Feed Fetcher | `Feed Fetcher` | RSS/Podcast updaters | -| Monitoring & Analytics | `Monitoring & Analytics` | Uptime monitors | -| Page Preview | `Page Preview` | Facebook/Slack link preview | -| SEO | `Search Engine Optimization` | Google Lighthouse | -| Security | `Security` | Vulnerability scanners | -| Social Media Marketing | `Social Media Marketing` | Brandwatch | -| Webhooks | `Webhooks` | Payment processors | -| Other | `Other` | Uncategorized bots | - -## Best Practices - -- **ML Auto-Updates**: Enable on Enterprise for latest models -- **Start with Managed Challenge**: Test before blocking -- **Always exclude verified bots**: Use `not cf.bot_management.verified_bot` -- **Exempt corporate proxies**: For B2B traffic via `cf.bot_management.corporate_proxy` -- **Use static resource exception**: Improves performance, reduces overhead diff --git a/external/develop/devops/cloudflare/references/bot-management/gotchas.md b/external/develop/devops/cloudflare/references/bot-management/gotchas.md deleted file mode 100644 index 685bcbd..0000000 --- a/external/develop/devops/cloudflare/references/bot-management/gotchas.md +++ /dev/null @@ -1,114 +0,0 @@ -# Bot Management Gotchas - -## Common Errors - -### "Bot Score = 0" - -**Cause:** Bot Management didn't run (internal Cloudflare request, Worker routing to zone (Orange-to-Orange), or request handled before BM (Redirect Rules, etc.)) -**Solution:** Check request flow and ensure Bot Management runs in request lifecycle - -### "JavaScript Detections Not Working" - -**Cause:** `js_detection.passed` always false or undefined due to: CSP headers don't allow `/cdn-cgi/challenge-platform/`, using on first page visit (needs HTML page first), ad blockers or disabled JS, JSD not enabled in dashboard, or using Block action (must use Managed Challenge) -**Solution:** Add CSP header `Content-Security-Policy: script-src 'self' /cdn-cgi/challenge-platform/;` and ensure JSD is enabled with Managed Challenge action - -### "False Positives (Legitimate Users Blocked)" - -**Cause:** Bot detection incorrectly flagging legitimate users -**Solution:** Check Bot Analytics for affected IPs/paths, identify detection source (ML, Heuristics, etc.), create exception rule like `(cf.bot_management.score lt 30 and http.request.uri.path eq "/problematic-path")` with Action: Skip (Bot Management), or allowlist by IP/ASN/country - -### "False Negatives (Bots Not Caught)" - -**Cause:** Bots bypassing detection -**Solution:** Lower score threshold (30 → 50), enable JavaScript Detections, add JA3/JA4 fingerprinting rules, or use rate limiting as fallback - -### "Verified Bot Blocked" - -**Cause:** Search engine bot blocked by WAF Managed Rules (not just Bot Management) -**Solution:** Create WAF exception for specific rule ID and verify bot via reverse DNS - -### "Yandex Bot Blocked During IP Update" - -**Cause:** Yandex updates bot IPs; new IPs unrecognized for 48h during propagation -**Solution:** -1. Check Security Events for specific WAF rule ID blocking Yandex -2. Create WAF exception: - ```txt - (http.user_agent contains "YandexBot" and ip.src in {}) - Action: Skip (WAF Managed Ruleset) - ``` -3. Monitor Bot Analytics for 48h -4. Remove exception after propagation completes - -Issue resolves automatically after 48h. Contact Cloudflare Support if persists. - -### "JA3/JA4 Missing" - -**Cause:** Non-HTTPS traffic, Worker routing traffic, Orange-to-Orange traffic via Worker, or Bot Management skipped -**Solution:** JA3/JA4 only available for HTTPS/TLS traffic; check request routing - -**JA3/JA4 Not User-Unique:** Same browser/library version = same fingerprint -- Don't use for user identification -- Use for client profiling only -- Fingerprints change with browser updates - -## Bot Verification Methods - -Cloudflare verifies bots via: - -1. **Reverse DNS (IP validation):** Traditional method—bot IP resolves to expected domain -2. **Web Bot Auth:** Modern cryptographic verification—faster propagation - -When `verifiedBot=true`, bot passed at least one method. - -**Inactive verified bots:** IPs removed after 24h of no traffic. - -## Detection Engine Behavior - -| Engine | Score | Timing | Plan | Notes | -|--------|-------|--------|------|-------| -| Heuristics | Always 1 | Immediate | All | Known fingerprints—overrides ML | -| ML | 1-99 | Immediate | All | Majority of detections | -| Anomaly Detection | Influences | After baseline | Enterprise | Optional, baseline analysis | -| JavaScript Detections | Pass/fail | After JS | Pro+ | Headless browser detection | -| Cloudflare Service | N/A | N/A | Enterprise | Zero Trust internal source | - -**Priority:** Heuristics > ML—if heuristic matches, score=1 regardless of ML. - -## Limits - -| Limit | Value | Notes | -|-------|-------|-------| -| Bot Score = 0 | Means not computed | Not score = 100 | -| First request JSD data | May not be available | JSD data appears on subsequent requests | -| Score accuracy | Not 100% guaranteed | False positives/negatives possible | -| JSD on first HTML page visit | Not supported | Requires subsequent page load | -| JSD requirements | JavaScript-enabled browser | Won't work with JS disabled or ad blockers | -| JSD ETag stripping | Strips ETags from HTML responses | May affect caching behavior | -| JSD CSP compatibility | Requires specific CSP | Not compatible with some CSP configurations | -| JSD meta CSP tags | Not supported | Must use HTTP headers | -| JSD WebSocket support | Not supported | WebSocket endpoints won't work with JSD | -| JSD mobile app support | Native apps won't pass | Only works in browsers | -| JA3/JA4 traffic type | HTTPS/TLS only | Not available for non-HTTPS traffic | -| JA3/JA4 Worker routing | Missing for Worker-routed traffic | Check request routing | -| JA3/JA4 uniqueness | Not unique per user | Shared by clients with same browser/library | -| JA3/JA4 stability | Can change with updates | Browser/library updates affect fingerprints | -| WAF custom rules (Free) | 5 | Varies by plan | -| WAF custom rules (Pro) | 20 | Varies by plan | -| WAF custom rules (Business) | 100 | Varies by plan | -| WAF custom rules (Enterprise) | 1,000+ | Varies by plan | -| Workers CPU time | Varies by plan | Applies to bot logic | -| Bot Analytics sampling | 1-10% adaptive | High-volume zones sampled more aggressively | -| Bot Analytics history | 30 days max | Historical data retention limit | -| CSP requirements for JSD | Must allow `/cdn-cgi/challenge-platform/` | Required for JSD to function | - -### Plan Restrictions - -| Feature | Free | Pro/Business | Enterprise | -|---------|------|--------------|------------| -| Granular scores (1-99) | No | No | Yes | -| JA3/JA4 | No | No | Yes | -| Anomaly Detection | No | No | Yes | -| Corporate Proxy detection | No | No | Yes | -| Verified bot categories | Limited | Limited | Full | -| Custom WAF rules | 5 | 20/100 | 1,000+ | diff --git a/external/develop/devops/cloudflare/references/bot-management/patterns.md b/external/develop/devops/cloudflare/references/bot-management/patterns.md deleted file mode 100644 index 4ca7085..0000000 --- a/external/develop/devops/cloudflare/references/bot-management/patterns.md +++ /dev/null @@ -1,182 +0,0 @@ -# Bot Management Patterns - -## E-commerce Protection - -```txt -# High security for checkout -(cf.bot_management.score lt 50 and http.request.uri.path in {"/checkout" "/cart/add"} and not cf.bot_management.verified_bot and not cf.bot_management.corporate_proxy) -Action: Managed Challenge -``` - -## API Protection - -```txt -# Protect API with JS detection + score -(http.request.uri.path matches "^/api/" and (cf.bot_management.score lt 30 or not cf.bot_management.js_detection.passed) and not cf.bot_management.verified_bot) -Action: Block -``` - -## SEO-Friendly Bot Handling - -```txt -# Allow search engine crawlers -(cf.bot_management.score lt 30 and not cf.verified_bot_category in {"Search Engine Crawler"}) -Action: Managed Challenge -``` - -## Block AI Scrapers - -```txt -# Block training crawlers only (allow AI assistants/search) -(cf.verified_bot_category eq "AI Crawler") -Action: Block - -# Block all AI-related bots (training + assistants + search) -(cf.verified_bot_category in {"AI Crawler" "AI Assistant" "AI Search"}) -Action: Block - -# Allow AI Search, block AI Crawler and AI Assistant -(cf.verified_bot_category in {"AI Crawler" "AI Assistant"}) -Action: Block - -# Or use dashboard: Security > Settings > Bot Management > Block AI Bots -``` - -## Rate Limiting by Bot Score - -```txt -# Stricter limits for suspicious traffic -(cf.bot_management.score lt 50) -Rate: 10 requests per 10 seconds - -(cf.bot_management.score ge 50) -Rate: 100 requests per 10 seconds -``` - -## Mobile App Allowlisting - -```txt -# Identify mobile app by JA3/JA4 -(cf.bot_management.ja4 in {"fingerprint1" "fingerprint2"}) -Action: Skip (all remaining rules) -``` - -## Datacenter Detection - -```typescript -import type { IncomingRequestCfProperties } from '@cloudflare/workers-types'; - -// Low score + not corporate proxy = likely datacenter bot -export default { - async fetch(request: Request): Promise { - const cf = request.cf as IncomingRequestCfProperties | undefined; - const botMgmt = cf?.botManagement; - - if (botMgmt?.score && botMgmt.score < 30 && - !botMgmt.corporateProxy && !botMgmt.verifiedBot) { - return new Response('Datacenter traffic blocked', { status: 403 }); - } - - return fetch(request); - } -}; -``` - -## Conditional Delay (Tarpit) - -```typescript -import type { IncomingRequestCfProperties } from '@cloudflare/workers-types'; - -// Add delay proportional to bot suspicion -export default { - async fetch(request: Request): Promise { - const cf = request.cf as IncomingRequestCfProperties | undefined; - const botMgmt = cf?.botManagement; - - if (botMgmt?.score && botMgmt.score < 50 && !botMgmt.verifiedBot) { - // Delay: 0-2 seconds for scores 50-0 - const delayMs = Math.max(0, (50 - botMgmt.score) * 40); - await new Promise(r => setTimeout(r, delayMs)); - } - - return fetch(request); - } -}; -``` - -## Layered Defense - -```txt -1. Bot Management (score-based) -2. JavaScript Detections (for JS-capable clients) -3. Rate Limiting (fallback protection) -4. WAF Managed Rules (OWASP, etc.) -``` - -## Progressive Enhancement - -```txt -Public content: High threshold (score < 10) -Authenticated: Medium threshold (score < 30) -Sensitive: Low threshold (score < 50) + JSD -``` - -## Zero Trust for Bots - -```txt -1. Default deny (all scores < 30) -2. Allowlist verified bots -3. Allowlist mobile apps (JA3/JA4) -4. Allowlist corporate proxies -5. Allowlist static resources -``` - -## Workers: Score + JS Detection - -```typescript -import type { IncomingRequestCfProperties } from '@cloudflare/workers-types'; - -export default { - async fetch(request: Request): Promise { - const cf = request.cf as IncomingRequestCfProperties | undefined; - const botMgmt = cf?.botManagement; - const url = new URL(request.url); - - if (botMgmt?.staticResource) return fetch(request); // Skip static - - // API endpoints: require JS detection + good score - if (url.pathname.startsWith('/api/')) { - const jsDetectionPassed = botMgmt?.jsDetection?.passed ?? false; - const score = botMgmt?.score ?? 100; - - if (!jsDetectionPassed || score < 30) { - return new Response('Unauthorized', { status: 401 }); - } - } - - return fetch(request); - } -}; -``` - -## Rate Limiting by JWT Claim + Bot Score - -```txt -# Enterprise: Combine bot score with JWT validation -Rate limiting > Custom rules -- Field: lookup_json_string(http.request.jwt.claims["{config_id}"][0], "sub") -- Matches: user ID claim -- Additional condition: cf.bot_management.score lt 50 -``` - -## WAF Integration Points - -- **WAF Custom Rules**: Primary enforcement mechanism -- **Rate Limiting Rules**: Bot score as dimension, stricter limits for low scores -- **Transform Rules**: Pass score to origin via custom header -- **Workers**: Programmatic bot logic, custom scoring algorithms -- **Page Rules / Configuration Rules**: Zone-level overrides, path-specific settings - -## See Also - -- [gotchas.md](./gotchas.md) - Common errors, false positives/negatives, limitations diff --git a/external/develop/devops/cloudflare/references/browser-rendering/api.md b/external/develop/devops/cloudflare/references/browser-rendering/api.md index cf037a3..7c78d33 100644 --- a/external/develop/devops/cloudflare/references/browser-rendering/api.md +++ b/external/develop/devops/cloudflare/references/browser-rendering/api.md @@ -4,9 +4,9 @@ Read the guide for the chosen interface for request schemas, return types, authe | Task | Documentation | |------|---------------| -| Screenshots, PDFs, HTML, scraping, or structured extraction | [Quick Actions](https://developers.cloudflare.com/browser-run/quick-actions/) — links to each action's request options and examples for REST or Workers bindings | -| Automate a browser in Workers with Puppeteer | [Puppeteer](https://developers.cloudflare.com/browser-run/puppeteer/) — Cloudflare package, browser operations, and session APIs | -| Automate a browser in Workers with Playwright | [Playwright](https://developers.cloudflare.com/browser-run/playwright/) — Cloudflare package, locators, storage state, and tracing | -| Control a remote browser from an external runtime | [CDP](https://developers.cloudflare.com/browser-run/cdp/) — session endpoints and links to Puppeteer, Playwright, and other clients | +| Screenshots, PDFs, HTML, scraping, or structured extraction | [Quick Actions](https://developers.cloudflare.com/browser-run/quick-actions/index.md) — links to each action's request options and examples for REST or Workers bindings | +| Automate a browser in Workers with Puppeteer | [Puppeteer](https://developers.cloudflare.com/browser-run/puppeteer/index.md) — Cloudflare package, browser operations, and session APIs | +| Automate a browser in Workers with Playwright | [Playwright](https://developers.cloudflare.com/browser-run/playwright/index.md) — Cloudflare package, locators, storage state, and tracing | +| Control a remote browser from an external runtime | [CDP](https://developers.cloudflare.com/browser-run/cdp/index.md) — session endpoints and links to Puppeteer, Playwright, and other clients | The product rename does not imply a rename of API paths or token permissions. Use the identifiers shown in the selected guide. diff --git a/external/develop/devops/cloudflare/references/browser-rendering/configuration.md b/external/develop/devops/cloudflare/references/browser-rendering/configuration.md index 8c1cbcc..71559a4 100644 --- a/external/develop/devops/cloudflare/references/browser-rendering/configuration.md +++ b/external/develop/devops/cloudflare/references/browser-rendering/configuration.md @@ -4,9 +4,9 @@ Check the project's runtime, installed client and Wrangler versions, and compati | Task | Documentation | |------|---------------| -| Start a project or configure REST authentication | [Get started](https://developers.cloudflare.com/browser-run/get-started/) — Quick Actions and browser session setup | -| Configure a Worker or choose a development mode | [Wrangler reference](https://developers.cloudflare.com/browser-run/reference/wrangler/) — browser bindings, compatibility requirements, and local/remote development | -| Install or update a Workers browser client | [Puppeteer](https://developers.cloudflare.com/browser-run/puppeteer/) or [Playwright](https://developers.cloudflare.com/browser-run/playwright/) — package-specific setup and supported versions | -| Connect from a script, server, or CI outside Workers | [CDP](https://developers.cloudflare.com/browser-run/cdp/) — authentication and client integration guides | +| Start a project or configure REST authentication | [Get started](https://developers.cloudflare.com/browser-run/get-started/index.md) — Quick Actions and browser session setup | +| Configure a Worker or choose a development mode | [Wrangler reference](https://developers.cloudflare.com/browser-run/reference/wrangler/index.md) — browser bindings, compatibility requirements, and local/remote development | +| Install or update a Workers browser client | [Puppeteer](https://developers.cloudflare.com/browser-run/puppeteer/index.md) or [Playwright](https://developers.cloudflare.com/browser-run/playwright/index.md) — package-specific setup and supported versions | +| Connect from a script, server, or CI outside Workers | [CDP](https://developers.cloudflare.com/browser-run/cdp/index.md) — authentication and client integration guides | Development support depends on the selected interface. Follow its current guidance rather than applying one remote-mode requirement to all Browser Run workflows. diff --git a/external/develop/devops/cloudflare/references/browser-rendering/gotchas.md b/external/develop/devops/cloudflare/references/browser-rendering/gotchas.md index 65ccff0..d1d37d7 100644 --- a/external/develop/devops/cloudflare/references/browser-rendering/gotchas.md +++ b/external/develop/devops/cloudflare/references/browser-rendering/gotchas.md @@ -4,12 +4,12 @@ Identify the integration and observed failure before changing timeouts or concur | Concern | Documentation | |---------|---------------| -| Quotas, launch rates, concurrency, and session timeouts | [Limits](https://developers.cloudflare.com/browser-run/limits/) — check the current plan and integration-specific limits | -| Browser hours and concurrent-browser charges | [Pricing](https://developers.cloudflare.com/browser-run/pricing/) — distinguish Quick Actions from browser sessions | -| Missing bindings, action failures, or unsupported behavior | [FAQ](https://developers.cloudflare.com/browser-run/faq/) — diagnose the reported error and runtime constraints | +| Quotas, launch rates, concurrency, and session timeouts | [Limits](https://developers.cloudflare.com/browser-run/limits/index.md) — check the current plan and integration-specific limits | +| Browser hours and concurrent-browser charges | [Pricing](https://developers.cloudflare.com/browser-run/pricing/index.md) — distinguish Quick Actions from browser sessions | +| Missing bindings, action failures, or unsupported behavior | [FAQ](https://developers.cloudflare.com/browser-run/faq/index.md) — diagnose the reported error and runtime constraints | | Puppeteer page evaluation cannot access outer variables | [JavaScript execution](https://pptr.dev/guides/javascript-execution) — browser execution context, passing arguments, and returned values | | Block resources or handle intercepted Puppeteer requests | [Request interception](https://pptr.dev/guides/network-interception) — continue, respond, or abort requests and avoid duplicate handling | -| Unexpected disconnects or session loss | [Browser close reasons](https://developers.cloudflare.com/browser-run/reference/browser-close-reasons/) — inspect the recorded close reason before choosing recovery | -| Development or compatibility failures | [Wrangler reference](https://developers.cloudflare.com/browser-run/reference/wrangler/) — verify binding configuration and interface-specific development support | +| Unexpected disconnects or session loss | [Browser close reasons](https://developers.cloudflare.com/browser-run/reference/browser-close-reasons/index.md) — inspect the recorded close reason before choosing recovery | +| Development or compatibility failures | [Wrangler reference](https://developers.cloudflare.com/browser-run/reference/wrangler/index.md) — verify binding configuration and interface-specific development support | Before increasing concurrency, check session cleanup and whether the workload can reuse browsers with appropriate isolation; see [patterns.md](patterns.md). Retrieve current limits and pricing when sizing a workload rather than relying on fixed tier tables. diff --git a/external/develop/devops/cloudflare/references/browser-rendering/patterns.md b/external/develop/devops/cloudflare/references/browser-rendering/patterns.md index c19a712..561e546 100644 --- a/external/develop/devops/cloudflare/references/browser-rendering/patterns.md +++ b/external/develop/devops/cloudflare/references/browser-rendering/patterns.md @@ -4,9 +4,9 @@ Use the current examples for the selected integration instead of translating bet | Task | Documentation | |------|---------------| -| Implement screenshots, PDFs, or extraction | [Quick Actions](https://developers.cloudflare.com/browser-run/quick-actions/) — choose the action and follow its example | -| Build custom interactions | [Puppeteer](https://developers.cloudflare.com/browser-run/puppeteer/) or [Playwright](https://developers.cloudflare.com/browser-run/playwright/) — browser automation examples | -| Reconnect across requests | [Reuse sessions](https://developers.cloudflare.com/browser-run/features/reuse-sessions/) — disconnect/reconnect lifecycle and when to use Durable Objects for stateful ownership | -| Share browser capacity while isolating users | [Concurrency and session isolation](https://developers.cloudflare.com/browser-run/limits/#how-can-i-manage-concurrency-and-session-isolation-with-browser-run) — tabs, browser contexts, and capacity tradeoffs | +| Implement screenshots, PDFs, or extraction | [Quick Actions](https://developers.cloudflare.com/browser-run/quick-actions/index.md) — choose the action and follow its example | +| Build custom interactions | [Puppeteer](https://developers.cloudflare.com/browser-run/puppeteer/index.md) or [Playwright](https://developers.cloudflare.com/browser-run/playwright/index.md) — browser automation examples | +| Reconnect across requests | [Reuse sessions](https://developers.cloudflare.com/browser-run/features/reuse-sessions/index.md) — disconnect/reconnect lifecycle and when to use Durable Objects for stateful ownership | +| Share browser capacity while isolating users | [Concurrency and session isolation](https://developers.cloudflare.com/browser-run/limits/index.md#how-can-i-manage-concurrency-and-session-isolation-with-browser-run) — tabs, browser contexts, and capacity tradeoffs | Quick Actions manage their own session lifecycle. For sessions managed by the application, close pages and browsers on completion or failure. If reuse is intentional, follow the client's disconnect/reconnect semantics and handle expired sessions; closing the browser ends it. Keep cookies and storage isolated between users, and coordinate ownership when several requests can reconnect to the same session. diff --git a/external/develop/devops/cloudflare/references/c3/README.md b/external/develop/devops/cloudflare/references/c3/README.md deleted file mode 100644 index 272fa43..0000000 --- a/external/develop/devops/cloudflare/references/c3/README.md +++ /dev/null @@ -1,112 +0,0 @@ -# C3 (create-cloudflare) - -Official CLI for scaffolding Cloudflare Workers and Pages projects with templates, TypeScript, and instant deployment. - -## Quick Start - -```bash -# Interactive (recommended for first-time) -npm create cloudflare@latest my-app - -# Worker (API/WebSocket/Cron) -npm create cloudflare@latest my-api -- --type=hello-world --ts - -# Pages (static/SSG) -npm create cloudflare@latest my-site -- --type=web-app --framework=astro --platform=pages -``` - -## Platform Decision Tree - -``` -What are you building? - -├─ API / WebSocket / Cron / Email handler -│ └─ Workers (default) - no --platform flag needed -│ npm create cloudflare@latest my-api -- --type=hello-world - -├─ Static site / SSG / Documentation -│ └─ Pages - requires --platform=pages -│ npm create cloudflare@latest my-site -- --type=web-app --framework=astro --platform=pages - -├─ Full-stack app (Next.js/Remix/SvelteKit) -│ └─ Follow the current framework guide below - -└─ Convert existing project - └─ npm create cloudflare@latest . -- --type=pre-existing --existing-script=./src/worker.ts -``` - -**Critical:** Pages projects require `--platform=pages` flag. Without it, C3 defaults to Workers. - -## Framework Setup - -Fetch the [Workers framework guide](https://developers.cloudflare.com/workers/framework-guides/) for the chosen framework before scaffolding or adapting an existing app. For Next.js, follow [Next.js on Workers](https://developers.cloudflare.com/workers/framework-guides/web-apps/nextjs/); use the [Pages static export guide](https://developers.cloudflare.com/pages/framework-guides/nextjs/deploy-a-static-nextjs-site/) only when targeting a Next.js static export on Pages. - -## Interactive Flow - -When run without flags, C3 prompts in this order: - -1. **Project name** - Directory to create (defaults to current dir with `.`) -2. **Application type** - `hello-world`, `web-app`, `demo`, `pre-existing`, `remote-template` -3. **Platform** - `workers` (default) or `pages` (for web apps only) -4. **Framework** - If web-app: `next`, `remix`, `astro`, `react-router`, `solid`, `svelte`, etc. -5. **TypeScript** - `yes` (recommended) or `no` -6. **Git** - Initialize repository? `yes` or `no` -7. **Deploy** - Deploy now? `yes` or `no` (requires `wrangler login`) - -## Installation Methods - -```bash -# NPM -npm create cloudflare@latest - -# Yarn -yarn create cloudflare - -# PNPM -pnpm create cloudflare@latest -``` - -## In This Reference - -| File | Purpose | Use When | -|------|---------|----------| -| **api.md** | Complete CLI flag reference | Scripting, CI/CD, advanced usage | -| **configuration.md** | Generated files, bindings, types | Understanding output, customization | -| **patterns.md** | Workflows, CI/CD, monorepos | Real-world integration | -| **gotchas.md** | Troubleshooting failures | Deployment blocked, errors | - -## Reading Order - -| Task | Read | -|------|------| -| Create first project | README only | -| Set up CI/CD | README → api → patterns | -| Debug failed deploy | gotchas | -| Understand generated files | configuration | -| Full CLI reference | api | -| Create custom template | patterns → configuration | -| Convert existing project | README → patterns | - -## Post-Creation - -```bash -cd my-app - -# Local dev with hot reload -npm run dev - -# Generate TypeScript types for bindings -npm run cf-typegen - -# Deploy to Cloudflare -npm run deploy -``` - -## See Also - -- **workers/README.md** - Workers runtime, bindings, APIs -- **workers-ai/README.md** - AI/ML models -- **pages/README.md** - Pages-specific features -- **wrangler/README.md** - Wrangler CLI beyond initial setup -- **d1/README.md** - SQLite database -- **r2/README.md** - Object storage diff --git a/external/develop/devops/cloudflare/references/c3/api.md b/external/develop/devops/cloudflare/references/c3/api.md deleted file mode 100644 index d28d3c4..0000000 --- a/external/develop/devops/cloudflare/references/c3/api.md +++ /dev/null @@ -1,70 +0,0 @@ -# C3 CLI Reference - -## Invocation - -```bash -npm create cloudflare@latest [name] [-- flags] # NPM requires -- -yarn create cloudflare [name] [flags] -pnpm create cloudflare@latest [name] [-- flags] -``` - -## Core Flags - -| Flag | Values | Description | -|------|--------|-------------| -| `--type` | `hello-world`, `web-app`, `demo`, `pre-existing`, `remote-template` | Application type | -| `--platform` | `workers` (default), `pages` | Target platform | -| `--framework` | `next`, `remix`, `astro`, `react-router`, `solid`, `svelte`, `qwik`, `vue`, `angular`, `hono` | Web framework (requires `--type=web-app`) | -| `--lang` | `ts`, `js`, `python` | Language (for `--type=hello-world`) | -| `--ts` / `--no-ts` | - | TypeScript for web apps | - -## Deployment Flags - -| Flag | Description | -|------|-------------| -| `--deploy` / `--no-deploy` | Deploy immediately (prompts interactive, skips in CI) | -| `--git` / `--no-git` | Initialize git (default: yes) | -| `--open` | Open browser after deploy | - -## Advanced Flags - -| Flag | Description | -|------|-------------| -| `--template=user/repo` | GitHub template or local path | -| `--existing-script=./src/worker.ts` | Existing script (requires `--type=pre-existing`) | -| `--category=ai\|database\|realtime` | Demo filter (requires `--type=demo`) | -| `--experimental` | Enable experimental features | -| `--wrangler-defaults` | Skip wrangler prompts | - -## Environment Variables - -```bash -CLOUDFLARE_API_TOKEN=xxx # For deployment -CLOUDFLARE_ACCOUNT_ID=xxx # Account ID -CF_TELEMETRY_DISABLED=1 # Disable telemetry -``` - -## Exit Codes - -`0` success, `1` user abort, `2` error - -## Examples - -For framework apps, follow [Framework Setup](README.md#framework-setup). - -```bash -# TypeScript Worker -npm create cloudflare@latest my-api -- --type=hello-world --lang=ts --no-deploy - -# Astro blog -npm create cloudflare@latest my-blog -- --type=web-app --framework=astro --ts --deploy - -# CI: non-interactive -npm create cloudflare@latest my-api -- --type=hello-world --lang=ts --no-git --no-deploy - -# GitHub template -npm create cloudflare@latest -- --template=cloudflare/templates/worker-openapi - -# Convert existing project -npm create cloudflare@latest . -- --type=pre-existing --existing-script=./build/worker.js -``` diff --git a/external/develop/devops/cloudflare/references/c3/configuration.md b/external/develop/devops/cloudflare/references/c3/configuration.md deleted file mode 100644 index 37f9f82..0000000 --- a/external/develop/devops/cloudflare/references/c3/configuration.md +++ /dev/null @@ -1,81 +0,0 @@ -# C3 Generated Configuration - -## Output Structure - -``` -my-app/ -├── src/index.ts # Worker entry point -├── wrangler.jsonc # Cloudflare config -├── package.json # Scripts -├── tsconfig.json -└── .gitignore -``` - -## wrangler.jsonc - -```jsonc -{ - "$schema": "https://raw.githubusercontent.com/cloudflare/workers-sdk/main/packages/wrangler/config-schema.json", - "name": "my-app", - "main": "src/index.ts", - "compatibility_date": "2026-01-27" -} -``` - -## Binding Placeholders - -C3 generates **placeholder IDs** that must be replaced before deploy: - -```jsonc -{ - "kv_namespaces": [{ "binding": "MY_KV", "id": "placeholder_kv_id" }], - "d1_databases": [{ "binding": "DB", "database_id": "00000000-..." }] -} -``` - -**Replace with real IDs:** -```bash -npx wrangler kv namespace create MY_KV # Returns real ID -npx wrangler d1 create my-database # Returns real database_id -``` - -**Deployment error if not replaced:** -``` -Error: Invalid KV namespace ID "placeholder_kv_id" -``` - -## Scripts - -```json -{ - "scripts": { - "dev": "wrangler dev", - "deploy": "wrangler deploy", - "cf-typegen": "wrangler types" - } -} -``` - -## Type Generation - -Run after adding bindings: -```bash -npm run cf-typegen -``` - -Generates `.wrangler/types/runtime.d.ts`: -```typescript -interface Env { - MY_KV: KVNamespace; - DB: D1Database; -} -``` - -## Post-Creation Checklist - -1. Review `wrangler.jsonc` - check name, compatibility_date -2. Replace placeholder binding IDs with real resource IDs -3. Run `npm run cf-typegen` -4. Test: `npm run dev` -5. Deploy: `npm run deploy` -6. Add secrets: `npx wrangler secret put SECRET_NAME` diff --git a/external/develop/devops/cloudflare/references/c3/gotchas.md b/external/develop/devops/cloudflare/references/c3/gotchas.md deleted file mode 100644 index ecd664d..0000000 --- a/external/develop/devops/cloudflare/references/c3/gotchas.md +++ /dev/null @@ -1,92 +0,0 @@ -# C3 Troubleshooting - -## Deployment Issues - -### Placeholder IDs - -**Error:** "Invalid namespace ID" -**Fix:** Replace placeholders in wrangler.jsonc with real IDs: -```bash -npx wrangler kv namespace create MY_KV # Get real ID -``` - -### Authentication - -**Error:** "Not authenticated" -**Fix:** `npx wrangler login` or set `CLOUDFLARE_API_TOKEN` - -### Name Conflict - -**Error:** "Worker already exists" -**Fix:** Change `name` in wrangler.jsonc - -## Platform Selection - -| Need | Platform | -|------|----------| -| Git integration, branch previews | `--platform=pages` | -| Durable Objects, D1, Queues | Workers (default) | - -Wrong platform? Recreate with correct `--platform` flag. - -## TypeScript Issues - -**"Cannot find name 'KVNamespace'"** -```bash -npm run cf-typegen # Regenerate types -# Restart TS server in editor -``` - -**Missing types after config change:** Re-run `npm run cf-typegen` - -## Package Manager - -**Multiple lockfiles causing issues:** -```bash -rm pnpm-lock.yaml # If using npm -rm package-lock.json # If using pnpm -``` - -## CI/CD - -**CI hangs on prompts:** -```bash -npm create cloudflare@latest my-app -- \ - --type=hello-world --lang=ts --no-git --no-deploy -``` - -**Auth in CI:** -```yaml -env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} -``` - -## Framework-Specific - -| Framework | Issue | Fix | -|-----------|-------|-----| -| Next.js | create-next-app failed | `npm cache clean --force`, retry | -| Astro | Adapter missing | Install `@astrojs/cloudflare` | -| Remix | Module errors | Update `@remix-run/cloudflare*` | - -## Compatibility Date - -**"Feature X requires compatibility_date >= ..."** -**Fix:** Update `compatibility_date` in wrangler.jsonc to today's date - -## Node.js Version - -**"Node.js version not supported"** -**Fix:** Install Node.js 18+ (`nvm install 20`) - -## Quick Reference - -| Error | Cause | Fix | -|-------|-------|-----| -| Invalid namespace ID | Placeholder binding | Create resource, update config | -| Not authenticated | No login | `npx wrangler login` | -| Cannot find KVNamespace | Missing types | `npm run cf-typegen` | -| Worker already exists | Name conflict | Change `name` | -| CI hangs | Missing flags | Add --type, --lang, --no-deploy | -| Template not found | Bad name | Check cloudflare/templates | diff --git a/external/develop/devops/cloudflare/references/c3/patterns.md b/external/develop/devops/cloudflare/references/c3/patterns.md deleted file mode 100644 index c9e9f68..0000000 --- a/external/develop/devops/cloudflare/references/c3/patterns.md +++ /dev/null @@ -1,80 +0,0 @@ -# C3 Usage Patterns - -## Quick Workflows - -For framework apps, follow [Framework Setup](README.md#framework-setup). - -```bash -# TypeScript API Worker -npm create cloudflare@latest my-api -- --type=hello-world --lang=ts --deploy - -# Astro static site -npm create cloudflare@latest my-blog -- --type=web-app --framework=astro --platform=pages --ts -``` - -## CI/CD (GitHub Actions) - -```yaml -- name: Deploy - run: npm run deploy - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} -``` - -**Non-interactive requires:** -```bash ---type= # Required ---no-git # Recommended (CI already in git) ---no-deploy # Deploy separately with secrets ---framework= # For web-app ---ts / --no-ts # Required -``` - -## Monorepo - -C3 detects workspace config (`package.json` workspaces or `pnpm-workspace.yaml`). - -```bash -cd packages/ -npm create cloudflare@latest my-worker -- --type=hello-world --lang=ts --no-deploy -``` - -## Custom Templates - -```bash -# GitHub repo -npm create cloudflare@latest -- --template=username/repo -npm create cloudflare@latest -- --template=cloudflare/templates/worker-openapi - -# Local path -npm create cloudflare@latest my-app -- --template=../my-template -``` - -**Template requires `c3.config.json`:** -```json -{ - "name": "my-template", - "category": "hello-world", - "copies": [{ "path": "src/" }, { "path": "wrangler.jsonc" }], - "transforms": [{ "path": "package.json", "jsonc": { "name": "{{projectName}}" }}] -} -``` - -## Existing Projects - -```bash -# Add Cloudflare to existing Worker -npm create cloudflare@latest . -- --type=pre-existing --existing-script=./dist/index.js -``` - -For existing framework apps, follow [Framework Setup](README.md#framework-setup). - -## Post-Creation Checklist - -1. Review `wrangler.jsonc` - set `compatibility_date`, verify `name` -2. Create bindings: `wrangler kv namespace create`, `wrangler d1 create`, `wrangler r2 bucket create` -3. Generate types: `npm run cf-typegen` -4. Test: `npm run dev` -5. Deploy: `npm run deploy` -6. Set secrets: `wrangler secret put SECRET_NAME` diff --git a/external/develop/devops/cloudflare/references/cache-reserve/README.md b/external/develop/devops/cloudflare/references/cache-reserve/README.md deleted file mode 100644 index cdb4446..0000000 --- a/external/develop/devops/cloudflare/references/cache-reserve/README.md +++ /dev/null @@ -1,147 +0,0 @@ -# Cloudflare Cache Reserve - -**Persistent cache storage built on R2 for long-term content retention** - -## Smart Shield Integration - -Cache Reserve is part of **Smart Shield**, Cloudflare's comprehensive security and performance suite: - -- **Smart Shield Advanced tier**: Includes 2TB Cache Reserve storage -- **Standalone purchase**: Available separately if not using Smart Shield -- **Migration**: Existing standalone customers can migrate to Smart Shield bundles - -**Decision**: Already on Smart Shield Advanced? Cache Reserve is included. Otherwise evaluate standalone purchase vs Smart Shield upgrade. - -## Overview - -Cache Reserve is Cloudflare's persistent, large-scale cache storage layer built on R2. It acts as the ultimate upper-tier cache, storing cacheable content for extended periods (30+ days) to maximize cache hits, reduce origin egress fees, and shield origins from repeated requests for long-tail content. - -## Core Concepts - -### What is Cache Reserve? - -- **Persistent storage layer**: Built on R2, sits above tiered cache hierarchy -- **Long-term retention**: 30-day default retention, extended on each access -- **Automatic operation**: Works seamlessly with existing CDN, no code changes required -- **Origin shielding**: Dramatically reduces origin egress by serving cached content longer -- **Usage-based pricing**: Pay only for storage + read/write operations - -### Cache Hierarchy - -``` -Visitor Request - ↓ -Lower-Tier Cache (closest to visitor) - ↓ (on miss) -Upper-Tier Cache (closest to origin) - ↓ (on miss) -Cache Reserve (R2 persistent storage) - ↓ (on miss) -Origin Server -``` - -### How It Works - -1. **On cache miss**: Content fetched from origin �� written to Cache Reserve + edge caches simultaneously -2. **On edge eviction**: Content may be evicted from edge cache but remains in Cache Reserve -3. **On subsequent request**: If edge cache misses but Cache Reserve hits → content restored to edge caches -4. **Retention**: Assets remain in Cache Reserve for 30 days since last access (configurable via TTL) - -## When to Use Cache Reserve - -``` -Need persistent caching? -├─ High origin egress costs → Cache Reserve ✓ -├─ Long-tail content (archives, media libraries) → Cache Reserve ✓ -├─ Already using Smart Shield Advanced → Included! ✓ -├─ Video streaming with seeking (range requests) → ✗ Not supported -├─ Dynamic/personalized content → ✗ Use edge cache only -├─ Need per-request cache control from Workers → ✗ Use R2 directly -└─ Frequently updated content (< 10hr lifetime) → ✗ Not eligible -``` - -## Asset Eligibility - -Cache Reserve only stores assets meeting **ALL** criteria: - -- Cacheable per Cloudflare's standard rules -- Minimum 10-hour TTL (36000 seconds) -- `Content-Length` header present -- Original files only (not transformed images) - -### Eligibility Checklist - -Use this checklist to verify if an asset is eligible: - -- [ ] Zone has Cache Reserve enabled -- [ ] Zone has Tiered Cache enabled (required) -- [ ] Asset TTL ≥ 10 hours (36,000 seconds) -- [ ] `Content-Length` header present on origin response -- [ ] No `Set-Cookie` header (or uses private directive) -- [ ] `Vary` header is NOT `*` (can be `Accept-Encoding`) -- [ ] Not an image transformation variant (original images OK) -- [ ] Not a range request (no HTTP 206 support) -- [ ] Not O2O (Orange-to-Orange) proxied request - -**All boxes must be checked for Cache Reserve eligibility.** - -### Not Eligible - -- Assets with TTL < 10 hours -- Responses without `Content-Length` header -- Image transformation variants (original images are eligible) -- Responses with `Set-Cookie` headers -- Responses with `Vary: *` header -- Assets from R2 public buckets on same zone -- O2O (Orange-to-Orange) setup requests -- **Range requests** (video seeking, partial content downloads) - -## Quick Start - -```bash -# Enable via Dashboard -https://dash.cloudflare.com/caching/cache-reserve -# Click "Enable Storage Sync" or "Purchase" button -``` - -**Prerequisites:** -- Paid Cache Reserve plan or Smart Shield Advanced required -- Tiered Cache required for optimal performance - -## Essential Commands - -```bash -# Check Cache Reserve status -curl -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/cache/cache_reserve" \ - -H "Authorization: Bearer $API_TOKEN" - -# Enable Cache Reserve -curl -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/cache/cache_reserve" \ - -H "Authorization: Bearer $API_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"value": "on"}' - -# Check asset cache status -curl -I https://example.com/asset.jpg | grep -i cache -``` - -## In This Reference - -| Task | Files | -|------|-------| -| Evaluate if Cache Reserve fits your use case | README.md (this file) | -| Enable Cache Reserve for your zone | README.md + [configuration.md](./configuration.md) | -| Use with Workers (understand limitations) | [api.md](./api.md) | -| Setup via SDKs or IaC (TypeScript, Python, Terraform) | [configuration.md](./configuration.md) | -| Optimize costs and debug issues | [patterns.md](./patterns.md) + [gotchas.md](./gotchas.md) | -| Understand eligibility and troubleshoot | [gotchas.md](./gotchas.md) → [patterns.md](./patterns.md) | - -**Files:** -- [configuration.md](./configuration.md) - Setup, API, SDKs, and Cache Rules -- [api.md](./api.md) - Purging, monitoring, Workers integration -- [patterns.md](./patterns.md) - Best practices, cost optimization, debugging -- [gotchas.md](./gotchas.md) - Common issues, limitations, troubleshooting - -## See Also -- [r2](../r2/) - Cache Reserve built on R2 storage -- [workers](https://developers.cloudflare.com/workers/) - Workers integration with Cache API diff --git a/external/develop/devops/cloudflare/references/cache-reserve/api.md b/external/develop/devops/cloudflare/references/cache-reserve/api.md deleted file mode 100644 index 18c49d8..0000000 --- a/external/develop/devops/cloudflare/references/cache-reserve/api.md +++ /dev/null @@ -1,194 +0,0 @@ -# Cache Reserve API - -## Workers Integration - -``` -┌────────────────────────────────────────────────────────────────┐ -│ CRITICAL: Workers Cache API ≠ Cache Reserve │ -│ │ -│ • Workers caches.default / cache.put() → edge cache ONLY │ -│ • Cache Reserve → zone-level setting, automatic, no per-req │ -│ • You CANNOT selectively write to Cache Reserve from Workers │ -│ • Cache Reserve works with standard fetch(), not cache.put() │ -└────────────────────────────────────────────────────────────────┘ -``` - -Cache Reserve is a **zone-level configuration**, not a per-request API. It works automatically when enabled for the zone: - -### Standard Fetch (Recommended) - -```typescript -// Cache Reserve works automatically via standard fetch -export default { - async fetch(request: Request, env: Env): Promise { - // Standard fetch uses Cache Reserve automatically - return await fetch(request); - } -}; -``` - -### Cache API Limitations - -**IMPORTANT**: `cache.put()` is **NOT compatible** with Cache Reserve or Tiered Cache. - -```typescript -// ❌ WRONG: cache.put() bypasses Cache Reserve -const cache = caches.default; -let response = await cache.match(request); -if (!response) { - response = await fetch(request); - await cache.put(request, response.clone()); // Bypasses Cache Reserve! -} - -// ✅ CORRECT: Use standard fetch for Cache Reserve compatibility -return await fetch(request); - -// ✅ CORRECT: Use Cache API only for custom cache namespaces -const customCache = await caches.open('my-custom-cache'); -let response = await customCache.match(request); -if (!response) { - response = await fetch(request); - await customCache.put(request, response.clone()); // Custom cache OK -} -``` - -## Purging and Cache Management - -### Purge by URL (Instant) - -```typescript -// Purge specific URL from Cache Reserve immediately -const purgeCacheReserveByURL = async ( - zoneId: string, - apiToken: string, - urls: string[] -) => { - const response = await fetch( - `https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`, - { - method: 'POST', - headers: { - 'Authorization': `Bearer ${apiToken}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ files: urls }) - } - ); - return await response.json(); -}; - -// Example usage -await purgeCacheReserveByURL('zone123', 'token456', [ - 'https://example.com/image.jpg', - 'https://example.com/video.mp4' -]); -``` - -### Purge by Tag/Host/Prefix (Revalidation) - -```typescript -// Purge by cache tag - forces revalidation, not immediate removal -await fetch( - `https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`, - { - method: 'POST', - headers: { 'Authorization': `Bearer ${apiToken}`, 'Content-Type': 'application/json' }, - body: JSON.stringify({ tags: ['tag1', 'tag2'] }) - } -); -``` - -**Purge behavior:** -- **By URL**: Immediate removal from Cache Reserve + edge cache -- **By tag/host/prefix**: Revalidation only, assets remain in storage (costs continue) - -### Clear All Cache Reserve Data - -```typescript -// Requires Cache Reserve OFF first -await fetch( - `https://api.cloudflare.com/client/v4/zones/${zoneId}/cache/cache_reserve_clear`, - { method: 'POST', headers: { 'Authorization': `Bearer ${apiToken}` } } -); - -// Check status: GET same endpoint returns { state: "In-progress" | "Completed" } -``` - -**Process**: Disable Cache Reserve → Call clear endpoint → Wait up to 24hr → Re-enable - -## Monitoring and Analytics - -### Dashboard Analytics - -Navigate to **Caching > Cache Reserve** to view: - -- **Egress Savings**: Total bytes served from Cache Reserve vs origin egress cost saved -- **Requests Served**: Cache Reserve hits vs misses breakdown -- **Storage Used**: Current GB stored in Cache Reserve (billed monthly) -- **Operations**: Class A (writes) and Class B (reads) operation counts -- **Cost Tracking**: Estimated monthly costs based on current usage - -### Logpush Integration - -```typescript -// Logpush field: CacheReserveUsed (boolean) - filter for Cache Reserve hits -// Query Cache Reserve hits in analytics -const logpushQuery = ` - SELECT - ClientRequestHost, - COUNT(*) as requests, - SUM(EdgeResponseBytes) as bytes_served, - COUNT(CASE WHEN CacheReserveUsed = true THEN 1 END) as cache_reserve_hits, - COUNT(CASE WHEN CacheReserveUsed = false THEN 1 END) as cache_reserve_misses - FROM http_requests - WHERE Timestamp >= NOW() - INTERVAL '24 hours' - GROUP BY ClientRequestHost - ORDER BY requests DESC -`; - -// Filter only Cache Reserve hits -const crHitsQuery = ` - SELECT ClientRequestHost, COUNT(*) as requests, SUM(EdgeResponseBytes) as bytes - FROM http_requests - WHERE CacheReserveUsed = true AND Timestamp >= NOW() - INTERVAL '7 days' - GROUP BY ClientRequestHost - ORDER BY bytes DESC -`; -``` - -### GraphQL Analytics - -```graphql -query CacheReserveAnalytics($zoneTag: string, $since: string, $until: string) { - viewer { - zones(filter: { zoneTag: $zoneTag }) { - httpRequests1dGroups( - filter: { datetime_geq: $since, datetime_leq: $until } - limit: 1000 - ) { - dimensions { date } - sum { - cachedBytes - cachedRequests - bytes - requests - } - } - } - } -} -``` - -## Pricing - -```typescript -// Storage: $0.015/GB-month | Class A (writes): $4.50/M | Class B (reads): $0.36/M -// Cache miss: 1A + 1B | Cache hit: 1B | Assets >1GB: proportionally more ops -``` - -## See Also - -- [README](./README.md) - Overview and core concepts -- [Configuration](./configuration.md) - Setup and Cache Rules -- [Patterns](./patterns.md) - Best practices and optimization -- [Gotchas](./gotchas.md) - Common issues and troubleshooting diff --git a/external/develop/devops/cloudflare/references/cache-reserve/configuration.md b/external/develop/devops/cloudflare/references/cache-reserve/configuration.md deleted file mode 100644 index 84a6616..0000000 --- a/external/develop/devops/cloudflare/references/cache-reserve/configuration.md +++ /dev/null @@ -1,169 +0,0 @@ -# Cache Reserve Configuration - -## Dashboard Setup - -**Minimum steps to enable:** - -```bash -# Navigate to dashboard -https://dash.cloudflare.com/caching/cache-reserve - -# Click "Enable Storage Sync" or "Purchase" button -``` - -**Prerequisites:** -- Paid Cache Reserve plan or Smart Shield Advanced required -- Tiered Cache **required** for Cache Reserve to function optimally - -## API Configuration - -### REST API - -```bash -# Enable -curl -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/cache/cache_reserve" \ - -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \ - -d '{"value": "on"}' - -# Check status -curl -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/cache/cache_reserve" \ - -H "Authorization: Bearer $API_TOKEN" -``` - -### TypeScript SDK - -```bash -npm install cloudflare -``` - -```typescript -import Cloudflare from 'cloudflare'; - -const client = new Cloudflare({ - apiToken: process.env.CLOUDFLARE_API_TOKEN, -}); - -// Enable Cache Reserve -await client.cache.cacheReserve.edit({ - zone_id: 'abc123', - value: 'on', -}); - -// Get Cache Reserve status -const status = await client.cache.cacheReserve.get({ - zone_id: 'abc123', -}); -console.log(status.value); // 'on' or 'off' -``` - -### Python SDK - -```bash -pip install cloudflare -``` - -```python -from cloudflare import Cloudflare - -client = Cloudflare(api_token=os.environ.get("CLOUDFLARE_API_TOKEN")) - -# Enable Cache Reserve -client.cache.cache_reserve.edit( - zone_id="abc123", - value="on" -) - -# Get Cache Reserve status -status = client.cache.cache_reserve.get(zone_id="abc123") -print(status.value) # 'on' or 'off' -``` - -### Terraform - -```hcl -terraform { - required_providers { - cloudflare = { - source = "cloudflare/cloudflare" - version = "~> 4.0" - } - } -} - -provider "cloudflare" { - api_token = var.cloudflare_api_token -} - -resource "cloudflare_zone_cache_reserve" "example" { - zone_id = var.zone_id - enabled = true -} - -# Tiered Cache is required for Cache Reserve -resource "cloudflare_tiered_cache" "example" { - zone_id = var.zone_id - cache_type = "smart" -} -``` - -### Pulumi - -```typescript -import * as cloudflare from "@pulumi/cloudflare"; - -// Enable Cache Reserve -const cacheReserve = new cloudflare.ZoneCacheReserve("example", { - zoneId: zoneId, - enabled: true, -}); - -// Enable Tiered Cache (required) -const tieredCache = new cloudflare.TieredCache("example", { - zoneId: zoneId, - cacheType: "smart", -}); -``` - -### Required API Token Permissions - -- `Zone Settings Read` -- `Zone Settings Write` -- `Zone Read` -- `Zone Write` - -## Cache Rules Integration - -Control Cache Reserve eligibility via Cache Rules: - -```typescript -// Enable for static assets -{ - action: 'set_cache_settings', - action_parameters: { - cache_reserve: { eligible: true, minimum_file_ttl: 86400 }, - edge_ttl: { mode: 'override_origin', default: 86400 }, - cache: true - }, - expression: '(http.request.uri.path matches "\\.(jpg|png|webp|pdf|zip)$")' -} - -// Disable for APIs -{ - action: 'set_cache_settings', - action_parameters: { cache_reserve: { eligible: false } }, - expression: '(http.request.uri.path matches "^/api/")' -} - -// Create via API: PUT to zones/{zone_id}/rulesets/phases/http_request_cache_settings/entrypoint -``` - -## Wrangler Integration - -Cache Reserve works automatically with Workers deployed via Wrangler. No special wrangler.jsonc configuration needed - enable Cache Reserve via Dashboard or API for the zone. - -## See Also - -- [README](./README.md) - Overview and core concepts -- [API Reference](./api.md) - Purging and monitoring APIs -- [Patterns](./patterns.md) - Best practices and optimization -- [Gotchas](./gotchas.md) - Common issues and troubleshooting diff --git a/external/develop/devops/cloudflare/references/cache-reserve/gotchas.md b/external/develop/devops/cloudflare/references/cache-reserve/gotchas.md deleted file mode 100644 index 9995cf8..0000000 --- a/external/develop/devops/cloudflare/references/cache-reserve/gotchas.md +++ /dev/null @@ -1,132 +0,0 @@ -# Cache Reserve Gotchas - -## Common Errors - -### "Assets Not Being Cached in Cache Reserve" - -**Cause:** Asset is not cacheable, TTL < 10 hours, Content-Length header missing, or blocking headers present (Set-Cookie, Vary: *) -**Solution:** Ensure minimum TTL of 10+ hours (`Cache-Control: public, max-age=36000`), add Content-Length header, remove Set-Cookie header, and set `Vary: Accept-Encoding` (not *) - -### "Range Requests Not Working" (Video Seeking Fails) - -**Cause:** Cache Reserve does **NOT** support range requests (HTTP 206 Partial Content) -**Solution:** Range requests bypass Cache Reserve entirely. For video streaming with seeking: -- Use edge cache only (shorter TTLs) -- Consider R2 with direct access for range-heavy workloads -- Accept that seekable content won't benefit from Cache Reserve persistence - -### "Origin Bandwidth Higher Than Expected" - -**Cause:** Cache Reserve fetches **uncompressed** content from origin, even though it serves compressed to visitors -**Solution:** -- If origin charges by bandwidth, factor in uncompressed transfer costs -- Cache Reserve compresses for visitors automatically (saves visitor bandwidth) -- Compare: origin egress savings vs higher uncompressed fetch costs - -### "Cloudflare Images Not Caching with Cache Reserve" - -**Cause:** Cloudflare Images with `Vary: Accept` header (format negotiation) is incompatible with Cache Reserve -**Solution:** -- Cache Reserve silently skips images with Vary for format negotiation -- Original images (non-transformed) may still be eligible -- Use Cloudflare Images variants or edge cache for transformed images - -### "High Class A Operations Costs" - -**Cause:** Frequent cache misses, short TTLs, or frequent revalidation -**Solution:** Increase TTL for stable content (24+ hours), enable Tiered Cache to reduce direct Cache Reserve misses, or use stale-while-revalidate - -### "Purge Not Working as Expected" - -**Cause:** Purge by tag only triggers revalidation but doesn't remove from Cache Reserve storage -**Solution:** Use purge by URL for immediate removal, or disable Cache Reserve then clear all data for complete removal - -### "O2O (Orange-to-Orange) Assets Not Caching" - -**Cause:** Orange-to-Orange (proxied zone requesting another proxied zone on Cloudflare) bypasses Cache Reserve -**Solution:** -- **What is O2O**: Zone A (proxied) → Zone B (proxied), both on Cloudflare -- **Detection**: Check `cf-cache-status` for `BYPASS` and review request path -- **Workaround**: Use R2 or direct origin access instead of O2O proxy chains - -### "Cache Reserve must be OFF before clearing data" - -**Cause:** Attempting to clear Cache Reserve data while it's still enabled -**Solution:** Disable Cache Reserve first, wait briefly for propagation (5s), then clear data (can take up to 24 hours) - -## Limits - -| Limit | Value | Notes | -|-------|-------|-------| -| Minimum TTL | 10 hours (36000 seconds) | Assets with shorter TTL not eligible | -| Default retention | 30 days (2592000 seconds) | Configurable | -| Maximum file size | Same as R2 limits | No practical limit | -| Purge/clear time | Up to 24 hours | Complete propagation time | -| Plan requirement | Paid Cache Reserve or Smart Shield | Not available on free plans | -| Content-Length header | Required | Must be present for eligibility | -| Set-Cookie header | Blocks caching | Must not be present (or use private directive) | -| Vary header | Cannot be * | Can use Vary: Accept-Encoding | -| Image transformations | Variants not eligible | Original images only | -| Range requests | NOT supported | HTTP 206 bypasses Cache Reserve | -| Compression | Fetches uncompressed | Serves compressed to visitors | -| Worker control | Zone-level only | Cannot control per-request | -| O2O requests | Bypassed | Orange-to-Orange not eligible | - -## Additional Resources - -- **Official Docs**: https://developers.cloudflare.com/cache/advanced-configuration/cache-reserve/ -- **API Reference**: https://developers.cloudflare.com/api/resources/cache/subresources/cache_reserve/ -- **Cache Rules**: https://developers.cloudflare.com/cache/how-to/cache-rules/ -- **Workers Cache API**: https://developers.cloudflare.com/workers/runtime-apis/cache/ -- **R2 Documentation**: https://developers.cloudflare.com/r2/ -- **Smart Shield**: https://developers.cloudflare.com/smart-shield/ -- **Tiered Cache**: https://developers.cloudflare.com/cache/how-to/tiered-cache/ - -## Troubleshooting Flowchart - -Asset not caching in Cache Reserve? - -``` -1. Is Cache Reserve enabled for zone? - → No: Enable via Dashboard or API - → Yes: Continue to step 2 - -2. Is Tiered Cache enabled? - → No: Enable Tiered Cache (required!) - → Yes: Continue to step 3 - -3. Does asset have TTL ≥ 10 hours? - → No: Increase via Cache Rules (edge_ttl override) - → Yes: Continue to step 4 - -4. Is Content-Length header present? - → No: Fix origin to include Content-Length - → Yes: Continue to step 5 - -5. Is Set-Cookie header present? - → Yes: Remove Set-Cookie or scope appropriately - → No: Continue to step 6 - -6. Is Vary header set to *? - → Yes: Change to specific value (e.g., Accept-Encoding) - → No: Continue to step 7 - -7. Is this a range request? - → Yes: Range requests bypass Cache Reserve (not supported) - → No: Continue to step 8 - -8. Is this an O2O (Orange-to-Orange) request? - → Yes: O2O bypasses Cache Reserve - → No: Continue to step 9 - -9. Check Logpush CacheReserveUsed field - → Filter logs to see if assets ever hit Cache Reserve - → Verify cf-cache-status header (should be HIT after first request) -``` - -## See Also - -- [README](./README.md) - Overview and core concepts -- [Configuration](./configuration.md) - Setup and Cache Rules -- [API Reference](./api.md) - Purging and monitoring -- [Patterns](./patterns.md) - Best practices and optimization diff --git a/external/develop/devops/cloudflare/references/cache-reserve/patterns.md b/external/develop/devops/cloudflare/references/cache-reserve/patterns.md deleted file mode 100644 index 65f9488..0000000 --- a/external/develop/devops/cloudflare/references/cache-reserve/patterns.md +++ /dev/null @@ -1,197 +0,0 @@ -# Cache Reserve Patterns - -## Best Practices - -### 1. Always Enable Tiered Cache - -```typescript -// Cache Reserve is designed for use WITH Tiered Cache -const configuration = { - tieredCache: 'enabled', // Required for optimal performance - cacheReserve: 'enabled', // Works best with Tiered Cache - - hierarchy: [ - 'Lower-Tier Cache (visitor)', - 'Upper-Tier Cache (origin region)', - 'Cache Reserve (persistent)', - 'Origin' - ] -}; -``` - -### 2. Set Appropriate Cache-Control Headers - -```typescript -// Origin response headers for Cache Reserve eligibility -const originHeaders = { - 'Cache-Control': 'public, max-age=86400', // 24hr (minimum 10hr) - 'Content-Length': '1024000', // Required - 'Cache-Tag': 'images,product-123', // Optional: purging - 'ETag': '"abc123"', // Optional: revalidation - // Avoid: 'Set-Cookie' and 'Vary: *' prevent caching -}; -``` - -### 3. Use Cache Rules for Fine-Grained Control - -```typescript -// Different TTLs for different content types -const cacheRules = [ - { - description: 'Long-term cache for immutable assets', - expression: '(http.request.uri.path matches "^/static/.*\\.[a-f0-9]{8}\\.")', - action_parameters: { - cache_reserve: { eligible: true }, - edge_ttl: { mode: 'override_origin', default: 2592000 }, // 30 days - cache: true - } - }, - { - description: 'Moderate cache for regular images', - expression: '(http.request.uri.path matches "\\.(jpg|png|webp)$")', - action_parameters: { - cache_reserve: { eligible: true }, - edge_ttl: { mode: 'override_origin', default: 86400 }, // 24 hours - cache: true - } - }, - { - description: 'Exclude API from Cache Reserve', - expression: '(http.request.uri.path matches "^/api/")', - action_parameters: { cache_reserve: { eligible: false }, cache: false } - } -]; -``` - -### 4. Making Assets Cache Reserve Eligible from Workers - -**Note**: This modifies response headers to meet eligibility criteria but does NOT directly control Cache Reserve storage (which is zone-level automatic). - -```typescript -export default { - async fetch(request: Request, env: Env): Promise { - const response = await fetch(request); - if (!response.ok) return response; - - const headers = new Headers(response.headers); - headers.set('Cache-Control', 'public, max-age=36000'); // 10hr minimum - headers.delete('Set-Cookie'); // Blocks caching - - // Ensure Content-Length present - if (!headers.has('Content-Length')) { - const blob = await response.blob(); - headers.set('Content-Length', blob.size.toString()); - return new Response(blob, { status: response.status, headers }); - } - - return new Response(response.body, { status: response.status, headers }); - } -}; -``` - -### 5. Hostname Best Practices - -Use Worker's hostname for efficient caching - avoid overriding hostname unnecessarily. - -## Architecture Patterns - -### Multi-Tier Caching + Immutable Assets - -```typescript -// Optimal: L1 (visitor) → L2 (region) → L3 (Cache Reserve) → Origin -export default { - async fetch(request: Request, env: Env): Promise { - const url = new URL(request.url); - const isImmutable = /\.[a-f0-9]{8,}\.(js|css|jpg|png|woff2)$/.test(url.pathname); - const response = await fetch(request); - - if (isImmutable) { - const headers = new Headers(response.headers); - headers.set('Cache-Control', 'public, max-age=31536000, immutable'); - return new Response(response.body, { status: response.status, headers }); - } - return response; - } -}; -``` - -## Cost Optimization - -### Cost Calculator - -```typescript -interface CacheReserveEstimate { - avgAssetSizeGB: number; - uniqueAssets: number; - monthlyReads: number; - monthlyWrites: number; - originEgressCostPerGB: number; // e.g., AWS: $0.09/GB -} - -function estimateMonthlyCost(input: CacheReserveEstimate) { - // Cache Reserve pricing - const storageCostPerGBMonth = 0.015; - const classAPerMillion = 4.50; // writes - const classBPerMillion = 0.36; // reads - - // Calculate Cache Reserve costs - const totalStorageGB = input.avgAssetSizeGB * input.uniqueAssets; - const storageCost = totalStorageGB * storageCostPerGBMonth; - const writeCost = (input.monthlyWrites / 1_000_000) * classAPerMillion; - const readCost = (input.monthlyReads / 1_000_000) * classBPerMillion; - - const cacheReserveCost = storageCost + writeCost + readCost; - - // Calculate origin egress cost (what you'd pay without Cache Reserve) - const totalTrafficGB = (input.monthlyReads * input.avgAssetSizeGB); - const originEgressCost = totalTrafficGB * input.originEgressCostPerGB; - - // Savings calculation - const savings = originEgressCost - cacheReserveCost; - const savingsPercent = ((savings / originEgressCost) * 100).toFixed(1); - - return { - cacheReserveCost: `$${cacheReserveCost.toFixed(2)}`, - originEgressCost: `$${originEgressCost.toFixed(2)}`, - monthlySavings: `$${savings.toFixed(2)}`, - savingsPercent: `${savingsPercent}%`, - breakdown: { - storage: `$${storageCost.toFixed(2)}`, - writes: `$${writeCost.toFixed(2)}`, - reads: `$${readCost.toFixed(2)}`, - } - }; -} - -// Example: Media library -const mediaLibrary = estimateMonthlyCost({ - avgAssetSizeGB: 0.005, // 5MB images - uniqueAssets: 10_000, - monthlyReads: 5_000_000, - monthlyWrites: 50_000, - originEgressCostPerGB: 0.09, // AWS S3 -}); - -console.log(mediaLibrary); -// { -// cacheReserveCost: "$9.98", -// originEgressCost: "$25.00", -// monthlySavings: "$15.02", -// savingsPercent: "60.1%", -// breakdown: { storage: "$0.75", writes: "$0.23", reads: "$9.00" } -// } -``` - -### Optimization Guidelines - -- **Set appropriate TTLs**: 10hr minimum, 24hr+ optimal for stable content, 30d max cautiously -- **Cache high-value stable assets**: Images, media, fonts, archives, documentation -- **Exclude frequently changing**: APIs, user-specific content, real-time data -- **Compression note**: Cache Reserve fetches uncompressed from origin, serves compressed to visitors - factor in origin egress costs - -## See Also - -- [README](./README.md) - Overview and core concepts -- [Configuration](./configuration.md) - Setup and Cache Rules -- [API Reference](./api.md) - Purging and monitoring -- [Gotchas](./gotchas.md) - Common issues and troubleshooting diff --git a/external/develop/devops/cloudflare/references/containers/README.md b/external/develop/devops/cloudflare/references/containers/README.md index fc4a2c9..f6982ed 100644 --- a/external/develop/devops/cloudflare/references/containers/README.md +++ b/external/develop/devops/cloudflare/references/containers/README.md @@ -4,9 +4,9 @@ Use this reference for containerized applications on the Workers platform, inclu ## Choose the runtime -Use [Containers](https://developers.cloudflare.com/containers/) for existing container images, custom runtimes, system dependencies, full filesystem access, or workloads needing additional CPU and memory. Use [Workers](https://developers.cloudflare.com/workers/) when the application fits the Workers runtime without those requirements. +Use [Containers](https://developers.cloudflare.com/containers/index.md) for existing container images, custom runtimes, system dependencies, full filesystem access, or workloads needing additional CPU and memory. Use [Workers](https://developers.cloudflare.com/workers/index.md) when the application fits the Workers runtime without those requirements. -Containers are controlled through [Durable Objects](https://developers.cloudflare.com/durable-objects/). An instance's identity does not make its filesystem persistent: design for restarts and store durable data outside the container disk. Read [Container lifecycle](https://developers.cloudflare.com/containers/concepts/architecture/) and [Container interface](https://developers.cloudflare.com/containers/reference/container-class/) for the relationship between the process, its Durable Object, and persistent storage. +Containers are controlled through [Durable Objects](https://developers.cloudflare.com/durable-objects/index.md). An instance's identity does not make its filesystem persistent: design for restarts and store durable data outside the container disk. Read [Container lifecycle](https://developers.cloudflare.com/containers/concepts/architecture/index.md) and [Container interface](https://developers.cloudflare.com/containers/api/container-class/index.md) for the relationship between the process, its Durable Object, and persistent storage. ## Find the documentation for the task @@ -14,7 +14,7 @@ Read the linked page before writing code or configuration; use its current API, | Task | Start here | | --- | --- | -| Create a project and deploy the first container | [Get started](https://developers.cloudflare.com/containers/get-started/) | +| Create a project and deploy the first container | [Get started](https://developers.cloudflare.com/containers/get-started/index.md) | | Configure images, bindings, instance sizes, and deployments | [Configuration](configuration.md) | | Control startup, requests, lifecycle, and scheduling | [API](api.md) | | Choose routing or connect other services | [Patterns](patterns.md) | diff --git a/external/develop/devops/cloudflare/references/containers/api.md b/external/develop/devops/cloudflare/references/containers/api.md index 43f8b2b..af316e9 100644 --- a/external/develop/devops/cloudflare/references/containers/api.md +++ b/external/develop/devops/cloudflare/references/containers/api.md @@ -1,13 +1,13 @@ # Containers API -Use the [Container interface](https://developers.cloudflare.com/containers/reference/container-class/) for the current SDK methods, signatures, properties, and examples. Use the [Durable Object Container API](https://developers.cloudflare.com/durable-objects/api/container/) when working directly with the runtime rather than the SDK class. +Use the [Container interface](https://developers.cloudflare.com/containers/api/container-class/index.md) for the current SDK methods, signatures, properties, and examples. Use the [Durable Object Container API](https://developers.cloudflare.com/containers/api/durable-object-container/index.md) when working directly with the runtime rather than the SDK class. | Task | Documentation | | --- | --- | -| Forward HTTP or WebSocket requests | [Request methods](https://developers.cloudflare.com/containers/reference/container-class/#request-methods) | -| Start a process, wait for ports, stop, or destroy it | [Start and stop](https://developers.cloudflare.com/containers/reference/container-class/#start-and-stop) | -| React to startup, exit, errors, or idle expiry | [Lifecycle hooks](https://developers.cloudflare.com/containers/reference/container-class/#lifecycle-hooks) | -| Inspect state or keep background work active | [State and monitoring](https://developers.cloudflare.com/containers/reference/container-class/#state-and-monitoring) | -| Schedule callbacks without replacing the SDK's alarm handler | [Scheduling](https://developers.cloudflare.com/containers/reference/container-class/#scheduling) | -| Address named instances, select stateless instances, or switch request ports | [Utility functions](https://developers.cloudflare.com/containers/reference/container-class/#utility-functions) | -| Communicate over TCP from a Durable Object | [TCP port API](https://developers.cloudflare.com/durable-objects/api/container/#gettcpport) | +| Forward HTTP or WebSocket requests | [Request methods](https://developers.cloudflare.com/containers/api/container-class/index.md#request-methods) | +| Start a process, wait for ports, stop, or destroy it | [Start and stop](https://developers.cloudflare.com/containers/api/container-class/index.md#start-and-stop) | +| React to startup, exit, errors, or idle expiry | [Lifecycle hooks](https://developers.cloudflare.com/containers/api/container-class/index.md#lifecycle-hooks) | +| Inspect state or keep background work active | [State and monitoring](https://developers.cloudflare.com/containers/api/container-class/index.md#state-and-monitoring) | +| Schedule callbacks without replacing the SDK's alarm handler | [Scheduling](https://developers.cloudflare.com/containers/api/container-class/index.md#scheduling) | +| Address named instances, select stateless instances, or switch request ports | [Utility functions](https://developers.cloudflare.com/containers/api/container-class/index.md#utility-functions) | +| Communicate over TCP from a Durable Object | [TCP port API](https://developers.cloudflare.com/containers/api/durable-object-container/index.md#gettcpport) | diff --git a/external/develop/devops/cloudflare/references/containers/configuration.md b/external/develop/devops/cloudflare/references/containers/configuration.md index 303a03b..eafdd95 100644 --- a/external/develop/devops/cloudflare/references/containers/configuration.md +++ b/external/develop/devops/cloudflare/references/containers/configuration.md @@ -4,12 +4,12 @@ Read the relevant documentation before choosing configuration fields or resource | Task | Documentation | | --- | --- | -| Configure the container image, Durable Object binding, class, migrations, and instance count | [Wrangler Containers configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#containers) | -| Select a predefined size or configure custom CPU, memory, and disk | [Limits and instance types](https://developers.cloudflare.com/containers/platform/limits/) and [custom instance configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#custom-instance-types) | -| Set ports, readiness checks, idle timeout, entrypoint, or internet access | [Container properties](https://developers.cloudflare.com/containers/reference/container-class/#properties) | -| Set runtime variables or pass secrets per instance | [Environment variables](https://developers.cloudflare.com/containers/configuration/environment-variables/) and [environment variables and secrets example](https://developers.cloudflare.com/containers/examples/env-vars-and-secrets/) | -| Build images or use existing registry images | [Image management](https://developers.cloudflare.com/containers/guides/image-management/) | -| Run and iterate locally | [Local development](https://developers.cloudflare.com/containers/guides/local-dev/) | -| Deploy from a workstation or Workers Builds | [Deploy Containers](https://developers.cloudflare.com/containers/guides/deploy/) | -| Control image updates and replacement of running instances | [Rollouts](https://developers.cloudflare.com/containers/configuration/rollouts/) | -| Estimate resource and network costs | [Pricing](https://developers.cloudflare.com/containers/platform/pricing/) | +| Configure the container image, Durable Object binding, class, migrations, and instance count | [Wrangler Containers configuration](https://developers.cloudflare.com/workers/wrangler/configuration/index.md#containers) | +| Select a predefined size or configure custom CPU, memory, and disk | [Limits and instance types](https://developers.cloudflare.com/containers/platform/limits/index.md) and [custom instance configuration](https://developers.cloudflare.com/workers/wrangler/configuration/index.md#custom-instance-types) | +| Set ports, readiness checks, idle timeout, entrypoint, or internet access | [Container properties](https://developers.cloudflare.com/containers/api/container-class/index.md#properties) | +| Set runtime variables or pass secrets per instance | [Environment variables](https://developers.cloudflare.com/containers/configuration/environment-variables/index.md) and [environment variables and secrets example](https://developers.cloudflare.com/containers/examples/env-vars-and-secrets/index.md) | +| Build images or use existing registry images | [Image management](https://developers.cloudflare.com/containers/guides/image-management/index.md) | +| Run and iterate locally | [Local development](https://developers.cloudflare.com/containers/guides/local-dev/index.md) | +| Deploy from a workstation or Workers Builds | [Deploy Containers](https://developers.cloudflare.com/containers/guides/deploy/index.md) | +| Control image updates and replacement of running instances | [Rollouts](https://developers.cloudflare.com/containers/configuration/rollouts/index.md) | +| Estimate resource and network costs | [Pricing](https://developers.cloudflare.com/containers/platform/pricing/index.md) | diff --git a/external/develop/devops/cloudflare/references/containers/gotchas.md b/external/develop/devops/cloudflare/references/containers/gotchas.md index a34936e..54f3714 100644 --- a/external/develop/devops/cloudflare/references/containers/gotchas.md +++ b/external/develop/devops/cloudflare/references/containers/gotchas.md @@ -4,13 +4,13 @@ Use the current documentation to diagnose behavior instead of relying on copied | Symptom or concern | Documentation to read | | --- | --- | -| Startup timeout or unavailable port | [Start and stop](https://developers.cloudflare.com/containers/reference/container-class/#start-and-stop), [Container properties](https://developers.cloudflare.com/containers/reference/container-class/#properties), and [first-deploy provisioning](https://developers.cloudflare.com/containers/get-started/) | -| WebSocket forwarding fails | [WebSocket example](https://developers.cloudflare.com/containers/examples/websocket/) and [request methods](https://developers.cloudflare.com/containers/reference/container-class/#request-methods) | -| Background work stops on idle expiry | [Activity renewal](https://developers.cloudflare.com/containers/reference/container-class/#renewactivitytimeout) and [idle expiry hook](https://developers.cloudflare.com/containers/reference/container-class/#onactivityexpired) | -| Scheduled callbacks do not run | [Scheduling and alarm ownership](https://developers.cloudflare.com/containers/reference/container-class/#scheduling) | -| Shutdown cleanup or filesystem data loss | [Container shutdown and disk lifecycle](https://developers.cloudflare.com/containers/concepts/architecture/#container-shutdown) | -| Out-of-memory errors or resource exhaustion | [FAQ](https://developers.cloudflare.com/containers/faq/) and [limits and instance types](https://developers.cloudflare.com/containers/platform/limits/) | -| Instance count exceeded or unexpected request distribution | [Wrangler configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#containers) and [scaling and routing](https://developers.cloudflare.com/containers/configuration/scaling-and-routing/) | -| Worker and container image versions differ after deployment | [Deployment behavior](https://developers.cloudflare.com/containers/guides/deploy/) and [rollouts](https://developers.cloudflare.com/containers/configuration/rollouts/) | -| Local behavior differs from deployed behavior | [Local development](https://developers.cloudflare.com/containers/guides/local-dev/) | -| Logs, cold starts, or runtime availability questions | [FAQ](https://developers.cloudflare.com/containers/faq/) | +| Startup timeout or unavailable port | [Start and stop](https://developers.cloudflare.com/containers/api/container-class/index.md#start-and-stop), [Container properties](https://developers.cloudflare.com/containers/api/container-class/index.md#properties), and [first-deploy provisioning](https://developers.cloudflare.com/containers/get-started/index.md) | +| WebSocket forwarding fails | [WebSocket example](https://developers.cloudflare.com/containers/examples/websocket/index.md) and [request methods](https://developers.cloudflare.com/containers/api/container-class/index.md#request-methods) | +| Background work stops on idle expiry | [Activity renewal](https://developers.cloudflare.com/containers/api/container-class/index.md#renewactivitytimeout) and [idle expiry hook](https://developers.cloudflare.com/containers/api/container-class/index.md#onactivityexpired) | +| Scheduled callbacks do not run | [Scheduling and alarm ownership](https://developers.cloudflare.com/containers/api/container-class/index.md#scheduling) | +| Shutdown cleanup or filesystem data loss | [Container shutdown and disk lifecycle](https://developers.cloudflare.com/containers/concepts/architecture/index.md#container-shutdown) | +| Out-of-memory errors or resource exhaustion | [FAQ](https://developers.cloudflare.com/containers/faq/index.md) and [limits and instance types](https://developers.cloudflare.com/containers/platform/limits/index.md) | +| Instance count exceeded or unexpected request distribution | [Wrangler configuration](https://developers.cloudflare.com/workers/wrangler/configuration/index.md#containers) and [scaling and routing](https://developers.cloudflare.com/containers/configuration/scaling-and-routing/index.md) | +| Worker and container image versions differ after deployment | [Deployment behavior](https://developers.cloudflare.com/containers/guides/deploy/index.md) and [rollouts](https://developers.cloudflare.com/containers/configuration/rollouts/index.md) | +| Local behavior differs from deployed behavior | [Local development](https://developers.cloudflare.com/containers/guides/local-dev/index.md) | +| Logs, cold starts, or runtime availability questions | [FAQ](https://developers.cloudflare.com/containers/faq/index.md) | diff --git a/external/develop/devops/cloudflare/references/containers/patterns.md b/external/develop/devops/cloudflare/references/containers/patterns.md index e733b83..2d7ffc9 100644 --- a/external/develop/devops/cloudflare/references/containers/patterns.md +++ b/external/develop/devops/cloudflare/references/containers/patterns.md @@ -1,18 +1,18 @@ # Containers patterns -Choose instance identity based on the workload: per-user/session or per-job identities for affinity, one shared identity for a singleton, and interchangeable instances for stateless requests. Read [scaling and routing](https://developers.cloudflare.com/containers/configuration/scaling-and-routing/) for current helpers and scaling behavior before implementing that choice. +Choose instance identity based on the workload: per-user/session or per-job identities for affinity, one shared identity for a singleton, and interchangeable instances for stateless requests. Read [scaling and routing](https://developers.cloudflare.com/containers/configuration/scaling-and-routing/index.md) for current helpers and scaling behavior before implementing that choice. | Task | Documentation | | --- | --- | -| Distribute requests across stateless instances | [Stateless instances example](https://developers.cloudflare.com/containers/examples/stateless/) | -| Forward WebSocket connections | [WebSocket example](https://developers.cloudflare.com/containers/examples/websocket/) | -| React to lifecycle changes | [Status hooks example](https://developers.cloudflare.com/containers/examples/status-hooks/) | -| Handle shutdown and persist data across restarts | [Container lifecycle](https://developers.cloudflare.com/containers/concepts/architecture/) and [Container interface](https://developers.cloudflare.com/containers/reference/container-class/) | -| Keep long operations active or schedule callbacks | [Activity renewal](https://developers.cloudflare.com/containers/reference/container-class/#renewactivitytimeout) and [scheduling](https://developers.cloudflare.com/containers/reference/container-class/#scheduling) | -| Start containers on a cron schedule | [Cron container example](https://developers.cloudflare.com/containers/examples/cron/) | -| Route requests to multiple ports | [Request methods](https://developers.cloudflare.com/containers/reference/container-class/#request-methods) and [utility functions](https://developers.cloudflare.com/containers/reference/container-class/#utility-functions) | -| Access Workers bindings from the container | [Connect to Workers and bindings](https://developers.cloudflare.com/containers/configuration/workers-connections/) | +| Distribute requests across stateless instances | [Stateless instances example](https://developers.cloudflare.com/containers/examples/stateless/index.md) | +| Forward WebSocket connections | [WebSocket example](https://developers.cloudflare.com/containers/examples/websocket/index.md) | +| React to lifecycle changes | [Status hooks example](https://developers.cloudflare.com/containers/examples/status-hooks/index.md) | +| Handle shutdown and persist data across restarts | [Container lifecycle](https://developers.cloudflare.com/containers/concepts/architecture/index.md) and [Container interface](https://developers.cloudflare.com/containers/api/container-class/index.md) | +| Keep long operations active or schedule callbacks | [Activity renewal](https://developers.cloudflare.com/containers/api/container-class/index.md#renewactivitytimeout) and [scheduling](https://developers.cloudflare.com/containers/api/container-class/index.md#scheduling) | +| Start containers on a cron schedule | [Cron container example](https://developers.cloudflare.com/containers/examples/cron/index.md) | +| Route requests to multiple ports | [Request methods](https://developers.cloudflare.com/containers/api/container-class/index.md#request-methods) and [utility functions](https://developers.cloudflare.com/containers/api/container-class/index.md#utility-functions) | +| Access Workers bindings from the container | [Connect to Workers and bindings](https://developers.cloudflare.com/containers/configuration/workers-connections/index.md) | ## Workflows and Queues -For multi-step orchestration, combine the [Workflows Workers API](https://developers.cloudflare.com/workflows/build/workers-api/) with the [Container API](api.md). For queue-driven jobs, read the [Queues consumer API](https://developers.cloudflare.com/queues/configuration/javascript-apis/#consumer) and [acknowledgement and retry behavior](https://developers.cloudflare.com/queues/configuration/batching-retries/#explicit-acknowledgement-and-retries) alongside the Container API. These pages document the component APIs; they are not end-to-end Container integration examples. +For multi-step orchestration, combine the [Workflows Workers API](https://developers.cloudflare.com/workflows/build/workers-api/index.md) with the [Container API](api.md). For queue-driven jobs, read the [Queues consumer API](https://developers.cloudflare.com/queues/configuration/javascript-apis/index.md#consumer) and [acknowledgement and retry behavior](https://developers.cloudflare.com/queues/configuration/batching-retries/index.md#explicit-acknowledgement-and-retries) alongside the Container API. These pages document the component APIs; they are not end-to-end Container integration examples. diff --git a/external/develop/devops/cloudflare/references/cron-triggers/README.md b/external/develop/devops/cloudflare/references/cron-triggers/README.md index 8ad1747..b7104e4 100644 --- a/external/develop/devops/cloudflare/references/cron-triggers/README.md +++ b/external/develop/devops/cloudflare/references/cron-triggers/README.md @@ -2,10 +2,10 @@ Use Cron Triggers to start periodic Worker jobs. Fetch the relevant current documentation before implementing; configuration, API signatures, examples, and limits belong in the docs. -- **Set up a recurring job:** [Cron Triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/) covers scheduling, deployment, and execution history. -- **Implement the job:** [Scheduled handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/) covers controller properties, asynchronous work, and multiple schedules. -- **Schedule durable work:** [Trigger Workflows](https://developers.cloudflare.com/workflows/build/trigger-workflows/) covers direct Workflow schedules and starting instances from a Worker. Check this before introducing a Worker whose only job is to start a Workflow. -- **Check capacity:** fetch [Workers limits](https://developers.cloudflare.com/workers/platform/limits/) for the target plan and invocation type. +- **Set up a recurring job:** [Cron Triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/index.md) covers scheduling, deployment, and execution history. +- **Implement the job:** [Scheduled handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/index.md) covers controller properties, asynchronous work, and multiple schedules. +- **Schedule durable work:** [Trigger Workflows](https://developers.cloudflare.com/workflows/build/trigger-workflows/index.md) covers direct Workflow schedules and starting instances from a Worker. Check this before introducing a Worker whose only job is to start a Workflow. +- **Check capacity:** fetch [Workers limits](https://developers.cloudflare.com/workers/platform/limits/index.md) for the target plan and invocation type. ## In This Reference diff --git a/external/develop/devops/cloudflare/references/cron-triggers/api.md b/external/develop/devops/cloudflare/references/cron-triggers/api.md index 3f749e2..c363c67 100644 --- a/external/develop/devops/cloudflare/references/cron-triggers/api.md +++ b/external/develop/devops/cloudflare/references/cron-triggers/api.md @@ -4,12 +4,12 @@ Fetch the handler documentation before writing code; use its current language ex | Task | Documentation | | --- | --- | -| Implement the handler and access the cron expression, scheduled time, bindings, and context | [Scheduled handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/) | -| Route different schedules to different operations | [Handle multiple cron triggers](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/#handle-multiple-cron-triggers) | -| Await work and understand how asynchronous failures affect invocation status | [Handler methods](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/#methods) | -| Invoke a scheduled handler locally with a chosen expression or time | [Test Cron Triggers locally](https://developers.cloudflare.com/workers/configuration/cron-triggers/#test-cron-triggers-locally) | -| Build tests using runtime-backed controllers and execution contexts | [Workers test APIs](https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/) | -| Start and inspect a Workflow instance | [Trigger Workflows](https://developers.cloudflare.com/workflows/build/trigger-workflows/) | +| Implement the handler and access the cron expression, scheduled time, bindings, and context | [Scheduled handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/index.md) | +| Route different schedules to different operations | [Handle multiple cron triggers](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/index.md#handle-multiple-cron-triggers) | +| Await work and understand how asynchronous failures affect invocation status | [Handler methods](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/index.md#methods) | +| Invoke a scheduled handler locally with a chosen expression or time | [Test Cron Triggers locally](https://developers.cloudflare.com/workers/configuration/cron-triggers/index.md#test-cron-triggers-locally) | +| Build tests using runtime-backed controllers and execution contexts | [Workers test APIs](https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/index.md) | +| Start and inspect a Workflow instance | [Trigger Workflows](https://developers.cloudflare.com/workflows/build/trigger-workflows/index.md) | Decide which operation establishes successful completion and make its failures observable. Test each configured schedule and partial-failure recovery. Read the local-testing documentation for the current endpoint and query parameters instead of adding a production HTTP route to imitate the development helper. diff --git a/external/develop/devops/cloudflare/references/cron-triggers/configuration.md b/external/develop/devops/cloudflare/references/cron-triggers/configuration.md index a5772e6..a690183 100644 --- a/external/develop/devops/cloudflare/references/cron-triggers/configuration.md +++ b/external/develop/devops/cloudflare/references/cron-triggers/configuration.md @@ -4,12 +4,12 @@ Fetch the documentation for the configuration operation before changing schedule | Task | Documentation | | --- | --- | -| Add a handler and configure triggers, including per-environment schedules and deployment propagation | [Add a Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/#add-a-cron-trigger) | -| Choose an expression, interpret weekday numbering, or check supported extensions | [Supported cron expressions](https://developers.cloudflare.com/workers/configuration/cron-triggers/#supported-cron-expressions) | -| Remove schedules or distinguish omission from an empty configuration | [Remove a Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/#remove-a-cron-trigger) | -| Configure renewable-energy execution locations | [Green Compute](https://developers.cloudflare.com/workers/configuration/cron-triggers/#green-compute) | -| Check trigger counts and execution budgets | [Workers limits](https://developers.cloudflare.com/workers/platform/limits/) | -| Schedule Workflow instances directly | [Schedule a Workflow directly](https://developers.cloudflare.com/workflows/build/trigger-workflows/#schedule-a-workflow-directly) | +| Add a handler and configure triggers, including per-environment schedules and deployment propagation | [Add a Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/index.md#add-a-cron-trigger) | +| Choose an expression, interpret weekday numbering, or check supported extensions | [Supported cron expressions](https://developers.cloudflare.com/workers/configuration/cron-triggers/index.md#supported-cron-expressions) | +| Remove schedules or distinguish omission from an empty configuration | [Remove a Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/index.md#remove-a-cron-trigger) | +| Configure renewable-energy execution locations | [Green Compute](https://developers.cloudflare.com/workers/configuration/cron-triggers/index.md#green-compute) | +| Check trigger counts and execution budgets | [Workers limits](https://developers.cloudflare.com/workers/platform/limits/index.md) | +| Schedule Workflow instances directly | [Schedule a Workflow directly](https://developers.cloudflare.com/workflows/build/trigger-workflows/index.md#schedule-a-workflow-directly) | Identify the target environment and the intended business timezone before choosing an expression. Review which schedules a deployment will replace, and use the documented propagation behavior when planning a rollout. For Green Compute, follow its account-level configuration rather than inferring settings from Worker placement. diff --git a/external/develop/devops/cloudflare/references/cron-triggers/gotchas.md b/external/develop/devops/cloudflare/references/cron-triggers/gotchas.md index e013c86..fc2a054 100644 --- a/external/develop/devops/cloudflare/references/cron-triggers/gotchas.md +++ b/external/develop/devops/cloudflare/references/cron-triggers/gotchas.md @@ -4,13 +4,13 @@ Investigate using the current documentation rather than copied limits or assumed | Symptom or question | Documentation and check | | --- | --- | -| Job runs at an unexpected time | Check [UTC execution](https://developers.cloudflare.com/workers/configuration/cron-triggers/#background) and [expression syntax](https://developers.cloudflare.com/workers/configuration/cron-triggers/#supported-cron-expressions); compare with the intended business timezone. | -| Schedule is missing after deployment | Check the handler, target environment, and propagation guidance in [Add a Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/#add-a-cron-trigger). | -| Removing or preserving schedules has an unexpected result | Review [Remove a Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/#remove-a-cron-trigger) before changing empty or omitted configuration. | -| Local invocation fails | Follow [Test Cron Triggers locally](https://developers.cloudflare.com/workers/configuration/cron-triggers/#test-cron-triggers-locally) for the supported endpoint, port, and query parameters. | -| Async work fails or completion status is surprising | Read [handler methods](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/#methods) and inspect [past events](https://developers.cloudflare.com/workers/configuration/cron-triggers/#view-past-events). | -| Job exceeds its execution budget | Check [Workers limits](https://developers.cloudflare.com/workers/platform/limits/) before choosing a smaller unit of work or [Workflows](../workflows/README.md). | -| Green Compute behavior differs from expectations | Read [Green Compute](https://developers.cloudflare.com/workers/configuration/cron-triggers/#green-compute) for its execution-location policy and account configuration. | +| Job runs at an unexpected time | Check [UTC execution](https://developers.cloudflare.com/workers/configuration/cron-triggers/index.md#background) and [expression syntax](https://developers.cloudflare.com/workers/configuration/cron-triggers/index.md#supported-cron-expressions); compare with the intended business timezone. | +| Schedule is missing after deployment | Check the handler, target environment, and propagation guidance in [Add a Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/index.md#add-a-cron-trigger). | +| Removing or preserving schedules has an unexpected result | Review [Remove a Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/index.md#remove-a-cron-trigger) before changing empty or omitted configuration. | +| Local invocation fails | Follow [Test Cron Triggers locally](https://developers.cloudflare.com/workers/configuration/cron-triggers/index.md#test-cron-triggers-locally) for the supported endpoint, port, and query parameters. | +| Async work fails or completion status is surprising | Read [handler methods](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/index.md#methods) and inspect [past events](https://developers.cloudflare.com/workers/configuration/cron-triggers/index.md#view-past-events). | +| Job exceeds its execution budget | Check [Workers limits](https://developers.cloudflare.com/workers/platform/limits/index.md) before choosing a smaller unit of work or [Workflows](../workflows/README.md). | +| Green Compute behavior differs from expectations | Read [Green Compute](https://developers.cloudflare.com/workers/configuration/cron-triggers/index.md#green-compute) for its execution-location policy and account configuration. | For repeated or partially completed business operations, decide how to identify work and recover safely before selecting storage. Test recovery after each side effect; a marker alone does not establish that the operation completed. Do not assume a particular automatic retry schedule or delivery guarantee without a documented contract. diff --git a/external/develop/devops/cloudflare/references/cron-triggers/patterns.md b/external/develop/devops/cloudflare/references/cron-triggers/patterns.md index ffe19af..8312ebc 100644 --- a/external/develop/devops/cloudflare/references/cron-triggers/patterns.md +++ b/external/develop/devops/cloudflare/references/cron-triggers/patterns.md @@ -4,15 +4,15 @@ Choose the execution boundary before writing a scheduled job; fetch the relevant | Need | Documentation | | --- | --- | -| Periodic API sync, cleanup, reports, or health checks in a Worker | [Cron Triggers background](https://developers.cloudflare.com/workers/configuration/cron-triggers/#background) and [scheduled handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/) | -| Different jobs on different schedules | [Handle multiple cron triggers](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/#handle-multiple-cron-triggers) | -| Durable multi-step work started on a schedule | [Trigger Workflows](https://developers.cloudflare.com/workflows/build/trigger-workflows/) | -| Send work to a queue and implement its consumer | [Queues JavaScript APIs](https://developers.cloudflare.com/queues/configuration/javascript-apis/) | -| Coordinate state across invocations | [What are Durable Objects?](https://developers.cloudflare.com/durable-objects/concepts/what-are-durable-objects/) | -| Inspect whether scheduled work ran | [View past events](https://developers.cloudflare.com/workers/configuration/cron-triggers/#view-past-events) | +| Periodic API sync, cleanup, reports, or health checks in a Worker | [Cron Triggers background](https://developers.cloudflare.com/workers/configuration/cron-triggers/index.md#background) and [scheduled handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/index.md) | +| Different jobs on different schedules | [Handle multiple cron triggers](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/index.md#handle-multiple-cron-triggers) | +| Durable multi-step work started on a schedule | [Trigger Workflows](https://developers.cloudflare.com/workflows/build/trigger-workflows/index.md) | +| Send work to a queue and implement its consumer | [Queues JavaScript APIs](https://developers.cloudflare.com/queues/configuration/javascript-apis/index.md) | +| Coordinate state across invocations | [What are Durable Objects?](https://developers.cloudflare.com/durable-objects/concepts/what-are-durable-objects/index.md) | +| Inspect whether scheduled work ran | [View past events](https://developers.cloudflare.com/workers/configuration/cron-triggers/index.md#view-past-events) | Keep the trigger separate from the business operation so manual recovery and scheduled execution can share it. Decide how partial progress is recorded, how repeated attempts affect side effects, and who owns completion reporting. When distributing a batch, distinguish successful enqueueing from successful processing. -Use [Workers limits](https://developers.cloudflare.com/workers/platform/limits/) to evaluate whether the job fits one invocation. If it needs durable steps, waiting, or explicit retry boundaries, inspect [Workflows](../workflows/README.md) before building those mechanisms in the handler. +Use [Workers limits](https://developers.cloudflare.com/workers/platform/limits/index.md) to evaluate whether the job fits one invocation. If it needs durable steps, waiting, or explicit retry boundaries, inspect [Workflows](../workflows/README.md) before building those mechanisms in the handler. See [api.md](./api.md) for tests and [gotchas.md](./gotchas.md) for operational checks. diff --git a/external/develop/devops/cloudflare/references/d1/README.md b/external/develop/devops/cloudflare/references/d1/README.md index bed1f1d..734d93f 100644 --- a/external/develop/devops/cloudflare/references/d1/README.md +++ b/external/develop/devops/cloudflare/references/d1/README.md @@ -1,15 +1,15 @@ # Cloudflare D1 Database -Use D1 for managed relational application data with SQLite semantics. For an existing external database, consider [Hyperdrive](../hyperdrive/); for per-entity coordination, consider [Durable Objects](https://developers.cloudflare.com/workers/platform/storage-options/#sql-in-durable-objects-vs-d1). See [storage options](https://developers.cloudflare.com/workers/platform/storage-options/) before choosing a product. +Use D1 for managed relational application data with SQLite semantics. For an existing external database, consider [Hyperdrive](../hyperdrive/); for per-entity coordination, consider [Durable Objects](https://developers.cloudflare.com/workers/platform/storage-options/index.md#sql-in-durable-objects-vs-d1). See [storage options](https://developers.cloudflare.com/workers/platform/storage-options/index.md) before choosing a product. Read the relevant current documentation before implementing. These references route tasks to the source of truth rather than maintaining copies of APIs, configuration, or plan tables. ## Start here -- [Get started](https://developers.cloudflare.com/d1/get-started/): create a database, bind it to a Worker, and run a first query. +- [Get started](https://developers.cloudflare.com/d1/get-started/index.md): create a database, bind it to a Worker, and run a first query. - [configuration.md](./configuration.md): bindings, environments, migrations, local development, and ORM integration. - [api.md](./api.md): parameterized queries, batches, sessions, HTTP access, and testing. - [patterns.md](./patterns.md): query design, caching, tenant isolation, replication, and recovery. - [gotchas.md](./gotchas.md): errors, types, constraints, performance, and limits. -Check [limits](https://developers.cloudflare.com/d1/platform/limits/) and [pricing](https://developers.cloudflare.com/d1/platform/pricing/) for capacity, allowances, and plan availability; do not infer them from old examples. +Check [limits](https://developers.cloudflare.com/d1/platform/limits/index.md) and [pricing](https://developers.cloudflare.com/d1/platform/pricing/index.md) for capacity, allowances, and plan availability; do not infer them from old examples. diff --git a/external/develop/devops/cloudflare/references/d1/api.md b/external/develop/devops/cloudflare/references/d1/api.md index 8b019a0..c16c50e 100644 --- a/external/develop/devops/cloudflare/references/d1/api.md +++ b/external/develop/devops/cloudflare/references/d1/api.md @@ -4,14 +4,14 @@ Fetch the relevant API page before writing queries or assuming method signatures | Task | Current documentation | | --- | --- | -| Bind values and choose a query execution method | [Prepared statement methods](https://developers.cloudflare.com/d1/worker-api/prepared-statements/) | -| Execute batches and understand transaction rollback; use database sessions | [D1 Database API](https://developers.cloudflare.com/d1/worker-api/d1-database/) | -| Interpret results and query metadata | [Return objects](https://developers.cloudflare.com/d1/worker-api/return-object/) | -| Choose supported JavaScript values and TypeScript result types | [Workers Binding API](https://developers.cloudflare.com/d1/worker-api/) | -| Choose consistency constraints and carry bookmarks between requests | [Read replication and Sessions API](https://developers.cloudflare.com/d1/best-practices/read-replication/) | -| Query from a server-side script outside Workers | [REST query API](https://developers.cloudflare.com/api/resources/d1/subresources/database/methods/query/) | -| Handle query failures and transient errors | [Debug D1](https://developers.cloudflare.com/d1/observability/debug-d1/) and [retry queries](https://developers.cloudflare.com/d1/best-practices/retry-queries/) | -| Test database queries and apply migrations in tests | [Workers Vitest APIs: D1](https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/#d1) | +| Bind values and choose a query execution method | [Prepared statement methods](https://developers.cloudflare.com/d1/worker-api/prepared-statements/index.md) | +| Execute batches and understand transaction rollback; use database sessions | [D1 Database API](https://developers.cloudflare.com/d1/worker-api/d1-database/index.md) | +| Interpret results and query metadata | [Return objects](https://developers.cloudflare.com/d1/worker-api/return-object/index.md) | +| Choose supported JavaScript values and TypeScript result types | [Workers Binding API](https://developers.cloudflare.com/d1/worker-api/index.md) | +| Choose consistency constraints and carry bookmarks between requests | [Read replication and Sessions API](https://developers.cloudflare.com/d1/best-practices/read-replication/index.md) | +| Query from a server-side script outside Workers | [REST query API](https://developers.cloudflare.com/api/resources/d1/subresources/database/methods/query/index.md) | +| Handle query failures and transient errors | [Debug D1](https://developers.cloudflare.com/d1/observability/debug-d1/index.md) and [retry queries](https://developers.cloudflare.com/d1/best-practices/retry-queries/index.md) | +| Test database queries and apply migrations in tests | [Workers Vitest APIs: D1](https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/index.md#d1) | Bind untrusted values with prepared statements; do not interpolate them into SQL. Parameters do not replace identifiers: choose dynamic table, column, or sort names from an application-controlled allowlist. diff --git a/external/develop/devops/cloudflare/references/d1/configuration.md b/external/develop/devops/cloudflare/references/d1/configuration.md index c5d67e6..c53f409 100644 --- a/external/develop/devops/cloudflare/references/d1/configuration.md +++ b/external/develop/devops/cloudflare/references/d1/configuration.md @@ -4,15 +4,15 @@ Read the task's documentation before adding bindings or running database command | Task | Current documentation | | --- | --- | -| Create a database and attach a Worker binding | [Getting started](https://developers.cloudflare.com/d1/get-started/) | -| Configure binding fields and multiple databases | [Wrangler D1 configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#d1-databases) | -| Separate staging and production databases | [D1 environments](https://developers.cloudflare.com/d1/configuration/environments/) | -| Create, track, and apply schema migrations | [Migrations](https://developers.cloudflare.com/d1/reference/migrations/) | -| Look up CLI flags for management, execution, and exports | [D1 Wrangler commands](https://developers.cloudflare.com/d1/wrangler-commands/) | -| Develop against local database state | [Local development](https://developers.cloudflare.com/d1/best-practices/local-development/) | -| Generate binding types | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/) | -| Choose an ORM or query builder, including Drizzle | [D1 community projects](https://developers.cloudflare.com/d1/reference/community-projects/) (follow the integration's current setup guide) | -| Import or export SQL data | [Import and export data](https://developers.cloudflare.com/d1/best-practices/import-export-data/) | -| Enable replicas and use them through sessions | [Read replication](https://developers.cloudflare.com/d1/best-practices/read-replication/) | +| Create a database and attach a Worker binding | [Getting started](https://developers.cloudflare.com/d1/get-started/index.md) | +| Configure binding fields and multiple databases | [Wrangler D1 configuration](https://developers.cloudflare.com/workers/wrangler/configuration/index.md#d1-databases) | +| Separate staging and production databases | [D1 environments](https://developers.cloudflare.com/d1/configuration/environments/index.md) | +| Create, track, and apply schema migrations | [Migrations](https://developers.cloudflare.com/d1/reference/migrations/index.md) | +| Look up CLI flags for management, execution, and exports | [D1 Wrangler commands](https://developers.cloudflare.com/d1/wrangler-commands/index.md) | +| Develop against local database state | [Local development](https://developers.cloudflare.com/d1/best-practices/local-development/index.md) | +| Generate binding types | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/index.md) | +| Choose an ORM or query builder, including Drizzle | [D1 community projects](https://developers.cloudflare.com/d1/reference/community-projects/index.md) (follow the integration's current setup guide) | +| Import or export SQL data | [Import and export data](https://developers.cloudflare.com/d1/best-practices/import-export-data/index.md) | +| Enable replicas and use them through sessions | [Read replication](https://developers.cloudflare.com/d1/best-practices/read-replication/index.md) | Local migrations and data do not automatically update a remote database. Test against a separate staging database before a production migration. Naming another binding `DB_REPLICA` does not configure replica routing; follow the replication guide. diff --git a/external/develop/devops/cloudflare/references/d1/gotchas.md b/external/develop/devops/cloudflare/references/d1/gotchas.md index 7591502..dc21e22 100644 --- a/external/develop/devops/cloudflare/references/d1/gotchas.md +++ b/external/develop/devops/cloudflare/references/d1/gotchas.md @@ -4,15 +4,15 @@ Use current documentation to diagnose the failure before changing query or datab | Symptom or question | What to check | | --- | --- | -| Missing table, query exception, or constraint error | [Debug D1](https://developers.cloudflare.com/d1/observability/debug-d1/); verify the target binding, environment, and applied [migrations](https://developers.cloudflare.com/d1/reference/migrations/) | -| Boolean, date, or other binding type mismatch | [Workers Binding API type conversion](https://developers.cloudflare.com/d1/worker-api/) and [SQL support](https://developers.cloudflare.com/d1/sql-api/sql-statements/) | -| Foreign key failure during writes or migrations | [Foreign key enforcement and deferral](https://developers.cloudflare.com/d1/sql-api/foreign-keys/) | -| Slow queries, scans, or excessive rows read | [Indexes and query plans](https://developers.cloudflare.com/d1/best-practices/use-indexes/) and [metrics](https://developers.cloudflare.com/d1/observability/metrics-analytics/) | -| Query duration, statement, storage, or account limits | [Current limits](https://developers.cloudflare.com/d1/platform/limits/) | -| Unexpected usage charges or plan assumptions | [Pricing](https://developers.cloudflare.com/d1/platform/pricing/) | -| Stale reads after a write | [Sessions, bookmarks, and read replication](https://developers.cloudflare.com/d1/best-practices/read-replication/) | -| Transient query failures | [Retry guidance](https://developers.cloudflare.com/d1/best-practices/retry-queries/); check idempotency before retrying writes | -| Import/export failure or unsupported data | [Import/export behavior and limitations](https://developers.cloudflare.com/d1/best-practices/import-export-data/) | -| Local and deployed databases differ | [Local development](https://developers.cloudflare.com/d1/best-practices/local-development/) and [environment configuration](https://developers.cloudflare.com/d1/configuration/environments/) | +| Missing table, query exception, or constraint error | [Debug D1](https://developers.cloudflare.com/d1/observability/debug-d1/index.md); verify the target binding, environment, and applied [migrations](https://developers.cloudflare.com/d1/reference/migrations/index.md) | +| Boolean, date, or other binding type mismatch | [Workers Binding API type conversion](https://developers.cloudflare.com/d1/worker-api/index.md) and [SQL support](https://developers.cloudflare.com/d1/sql-api/sql-statements/index.md) | +| Foreign key failure during writes or migrations | [Foreign key enforcement and deferral](https://developers.cloudflare.com/d1/sql-api/foreign-keys/index.md) | +| Slow or expensive database, excessive rows read or written, or finding which databases and queries cost the most | [Find slow and expensive queries](https://developers.cloudflare.com/d1/observability/find-expensive-queries/index.md) with `cf sql query` or `wrangler d1 insights` (API tokens need **Account Analytics Read**); then [indexes and query plans](https://developers.cloudflare.com/d1/best-practices/use-indexes/index.md) | +| Query duration, statement, storage, or account limits | [Current limits](https://developers.cloudflare.com/d1/platform/limits/index.md) | +| Unexpected usage charges or plan assumptions | [Pricing](https://developers.cloudflare.com/d1/platform/pricing/index.md); [find the databases and queries that cost the most](https://developers.cloudflare.com/d1/observability/find-expensive-queries/index.md) | +| Stale reads after a write | [Sessions, bookmarks, and read replication](https://developers.cloudflare.com/d1/best-practices/read-replication/index.md) | +| Transient query failures | [Retry guidance](https://developers.cloudflare.com/d1/best-practices/retry-queries/index.md); check idempotency before retrying writes | +| Import/export failure or unsupported data | [Import/export behavior and limitations](https://developers.cloudflare.com/d1/best-practices/import-export-data/index.md) | +| Local and deployed databases differ | [Local development](https://developers.cloudflare.com/d1/best-practices/local-development/index.md) and [environment configuration](https://developers.cloudflare.com/d1/configuration/environments/index.md) | Continue to bind untrusted SQL values as described in [api.md](./api.md). Do not treat SQL injection as a recoverable database error or assume retries correct invalid queries. diff --git a/external/develop/devops/cloudflare/references/d1/patterns.md b/external/develop/devops/cloudflare/references/d1/patterns.md index 17353b9..faa5d49 100644 --- a/external/develop/devops/cloudflare/references/d1/patterns.md +++ b/external/develop/devops/cloudflare/references/d1/patterns.md @@ -4,14 +4,15 @@ Use these guides to design the operation, then fetch [api.md](./api.md) for impl | Task | Current documentation | | --- | --- | -| Design pagination, filters, joins, and aggregations | [Query a database](https://developers.cloudflare.com/d1/best-practices/query-d1/) and [supported SQL](https://developers.cloudflare.com/d1/sql-api/sql-statements/) | -| Reduce scans and inspect query plans | [Use indexes](https://developers.cloudflare.com/d1/best-practices/use-indexes/) | -| Batch writes or transform data | [Database API](https://developers.cloudflare.com/d1/worker-api/d1-database/) and [limits](https://developers.cloudflare.com/d1/platform/limits/) | -| Store and query event metadata | [Query JSON](https://developers.cloudflare.com/d1/sql-api/query-json/) | -| Evaluate a cache in front of D1 | [How KV works](https://developers.cloudflare.com/kv/concepts/how-kv-works/) | -| Choose shared or per-tenant databases | [D1 FAQs](https://developers.cloudflare.com/d1/reference/faq/) and [limits](https://developers.cloudflare.com/d1/platform/limits/) | -| Reduce read latency while preserving required consistency | [Read replication](https://developers.cloudflare.com/d1/best-practices/read-replication/) | -| Plan point-in-time recovery or portable backups | [Time Travel](https://developers.cloudflare.com/d1/reference/time-travel/) and [import/export](https://developers.cloudflare.com/d1/best-practices/import-export-data/) | +| Design pagination, filters, joins, and aggregations | [Query a database](https://developers.cloudflare.com/d1/best-practices/query-d1/index.md) and [supported SQL](https://developers.cloudflare.com/d1/sql-api/sql-statements/index.md) | +| Reduce scans and inspect query plans | [Use indexes](https://developers.cloudflare.com/d1/best-practices/use-indexes/index.md) | +| Find queries in production to index or cache | [Find slow and expensive queries](https://developers.cloudflare.com/d1/observability/find-expensive-queries/index.md) | +| Batch writes or transform data | [Database API](https://developers.cloudflare.com/d1/worker-api/d1-database/index.md) and [limits](https://developers.cloudflare.com/d1/platform/limits/index.md) | +| Store and query event metadata | [Query JSON](https://developers.cloudflare.com/d1/sql-api/query-json/index.md) | +| Evaluate a cache in front of D1 | [How KV works](https://developers.cloudflare.com/kv/concepts/how-kv-works/index.md) | +| Choose shared or per-tenant databases | [D1 FAQs](https://developers.cloudflare.com/d1/reference/faq/index.md) and [limits](https://developers.cloudflare.com/d1/platform/limits/index.md) | +| Reduce read latency while preserving required consistency | [Read replication](https://developers.cloudflare.com/d1/best-practices/read-replication/index.md) | +| Plan point-in-time recovery or portable backups | [Time Travel](https://developers.cloudflare.com/d1/reference/time-travel/index.md) and [import/export](https://developers.cloudflare.com/d1/best-practices/import-export-data/index.md) | Keep result sets bounded and pagination ordering deterministic. Choose indexes from actual query plans. When splitting a large operation into batches, account for the loss of whole-operation atomicity across batches. diff --git a/external/develop/devops/cloudflare/references/ddos/README.md b/external/develop/devops/cloudflare/references/ddos/README.md deleted file mode 100644 index 117dd21..0000000 --- a/external/develop/devops/cloudflare/references/ddos/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Cloudflare DDoS Protection - -Autonomous, always-on protection against DDoS attacks across L3/4 and L7. - -## Protection Types - -- **HTTP DDoS (L7)**: Protects HTTP/HTTPS traffic, phase `ddos_l7`, zone/account level -- **Network DDoS (L3/4)**: UDP/SYN/DNS floods, phase `ddos_l4`, account level only -- **Adaptive DDoS**: Learns 7-day baseline, detects deviations, 4 profile types (Origins, User-Agents, Locations, Protocols) - -## Plan Availability - -| Feature | Free | Pro | Business | Enterprise | Enterprise Advanced | -|---------|------|-----|----------|------------|---------------------| -| HTTP DDoS (L7) | ✓ | ✓ | ✓ | ✓ | ✓ | -| Network DDoS (L3/4) | ✓ | ✓ | ✓ | ✓ | ✓ | -| Override rules | 1 | 1 | 1 | 1 | 10 | -| Custom expressions | ✗ | ✗ | ✗ | ✗ | ✓ | -| Log action | ✗ | ✗ | ✗ | ✗ | ✓ | -| Adaptive DDoS | ✗ | ✗ | ✗ | ✓ | ✓ | -| Alert filters | Basic | Basic | Basic | Advanced | Advanced | - -## Actions & Sensitivity - -- **Actions**: `block`, `managed_challenge`, `challenge`, `log` (Enterprise Advanced only) -- **Sensitivity**: `default` (high), `medium`, `low`, `eoff` (essentially off) -- **Override**: By category/tag or individual rule ID -- **Scope**: Zone-level overrides take precedence over account-level - -## Reading Order - -| File | Purpose | Start Here If... | -|------|---------|------------------| -| [configuration.md](./configuration.md) | Dashboard setup, rule structure, adaptive profiles | You're setting up DDoS protection for the first time | -| [api.md](./api.md) | API endpoints, SDK usage, ruleset ID discovery | You're automating configuration or need programmatic access | -| [patterns.md](./patterns.md) | Protection strategies, defense-in-depth, dynamic response | You need implementation patterns or layered security | -| [gotchas.md](./gotchas.md) | False positives, tuning, error handling | You're troubleshooting or optimizing existing protection | - -## See Also -- [waf](../waf/) - Application-layer security rules -- [bot-management](../bot-management/) - Bot detection and mitigation diff --git a/external/develop/devops/cloudflare/references/ddos/api.md b/external/develop/devops/cloudflare/references/ddos/api.md deleted file mode 100644 index b96284a..0000000 --- a/external/develop/devops/cloudflare/references/ddos/api.md +++ /dev/null @@ -1,164 +0,0 @@ -# DDoS API - -## Endpoints - -### HTTP DDoS (L7) - -```typescript -// Zone-level -PUT /zones/{zoneId}/rulesets/phases/ddos_l7/entrypoint -GET /zones/{zoneId}/rulesets/phases/ddos_l7/entrypoint - -// Account-level (Enterprise Advanced) -PUT /accounts/{accountId}/rulesets/phases/ddos_l7/entrypoint -GET /accounts/{accountId}/rulesets/phases/ddos_l7/entrypoint -``` - -### Network DDoS (L3/4) - -```typescript -// Account-level only -PUT /accounts/{accountId}/rulesets/phases/ddos_l4/entrypoint -GET /accounts/{accountId}/rulesets/phases/ddos_l4/entrypoint -``` - -## TypeScript SDK - -**SDK Version**: Requires `cloudflare` >= 3.0.0 for ruleset phase methods. - -```typescript -import Cloudflare from "cloudflare"; - -const client = new Cloudflare({ apiToken: process.env.CLOUDFLARE_API_TOKEN }); - -// STEP 1: Discover managed ruleset ID (required for overrides) -const allRulesets = await client.rulesets.list({ zone_id: zoneId }); -const ddosRuleset = allRulesets.result.find( - (r) => r.kind === "managed" && r.phase === "ddos_l7" -); -if (!ddosRuleset) throw new Error("DDoS managed ruleset not found"); -const managedRulesetId = ddosRuleset.id; - -// STEP 2: Get current HTTP DDoS configuration -const entrypointRuleset = await client.zones.rulesets.phases.entrypoint.get("ddos_l7", { - zone_id: zoneId, -}); - -// STEP 3: Update HTTP DDoS ruleset with overrides -await client.zones.rulesets.phases.entrypoint.update("ddos_l7", { - zone_id: zoneId, - rules: [ - { - action: "execute", - expression: "true", - action_parameters: { - id: managedRulesetId, // From discovery step - overrides: { - sensitivity_level: "medium", - action: "managed_challenge", - }, - }, - }, - ], -}); - -// Network DDoS (account level, L3/4) -const l4Rulesets = await client.rulesets.list({ account_id: accountId }); -const l4DdosRuleset = l4Rulesets.result.find( - (r) => r.kind === "managed" && r.phase === "ddos_l4" -); -const l4Ruleset = await client.accounts.rulesets.phases.entrypoint.get("ddos_l4", { - account_id: accountId, -}); -``` - -## Alert Configuration - -```typescript -interface DDoSAlertConfig { - name: string; - enabled: boolean; - alert_type: "http_ddos_attack_alert" | "layer_3_4_ddos_attack_alert" - | "advanced_http_ddos_attack_alert" | "advanced_layer_3_4_ddos_attack_alert"; - filters?: { - zones?: string[]; - hostnames?: string[]; - requests_per_second?: number; - packets_per_second?: number; - megabits_per_second?: number; - ip_prefixes?: string[]; // CIDR - ip_addresses?: string[]; - protocols?: string[]; - }; - mechanisms: { - email?: Array<{ id: string }>; - webhooks?: Array<{ id: string }>; - pagerduty?: Array<{ id: string }>; - }; -} - -// Create alert -await fetch( - `https://api.cloudflare.com/client/v4/accounts/${accountId}/alerting/v3/policies`, - { - method: "POST", - headers: { - Authorization: `Bearer ${apiToken}`, - "Content-Type": "application/json", - }, - body: JSON.stringify(alertConfig), - } -); -``` - -## Typed Override Examples - -```typescript -// Override by category -interface CategoryOverride { - action: "execute"; - expression: string; - action_parameters: { - id: string; - overrides: { - categories?: Array<{ - category: "http-flood" | "http-anomaly" | "udp-flood" | "syn-flood"; - sensitivity_level?: "default" | "medium" | "low" | "eoff"; - action?: "block" | "managed_challenge" | "challenge" | "log"; - }>; - }; - }; -} - -// Override by rule ID -interface RuleOverride { - action: "execute"; - expression: string; - action_parameters: { - id: string; - overrides: { - rules?: Array<{ - id: string; - action?: "block" | "managed_challenge" | "challenge" | "log"; - sensitivity_level?: "default" | "medium" | "low" | "eoff"; - }>; - }; - }; -} - -// Example: Override specific adaptive rule -const adaptiveOverride: RuleOverride = { - action: "execute", - expression: "true", - action_parameters: { - id: managedRulesetId, - overrides: { - rules: [ - { id: "...adaptive-origins-rule-id...", sensitivity_level: "low" }, - ], - }, - }, -}; -``` - -See [patterns.md](./patterns.md) for complete implementation patterns. diff --git a/external/develop/devops/cloudflare/references/ddos/configuration.md b/external/develop/devops/cloudflare/references/ddos/configuration.md deleted file mode 100644 index 14c6e32..0000000 --- a/external/develop/devops/cloudflare/references/ddos/configuration.md +++ /dev/null @@ -1,93 +0,0 @@ -# DDoS Configuration - -## Dashboard Setup - -1. Navigate to Security > DDoS -2. Select HTTP DDoS or Network-layer DDoS -3. Configure sensitivity & action per ruleset/category/rule -4. Apply overrides with optional expressions (Enterprise Advanced) -5. Enable Adaptive DDoS toggle (Enterprise/Enterprise Advanced, requires 7 days traffic history) - -## Rule Structure - -```typescript -interface DDoSOverride { - description: string; - rules: Array<{ - action: "execute"; - expression: string; // Custom expression (Enterprise Advanced) or "true" for all - action_parameters: { - id: string; // Managed ruleset ID (discover via api.md) - overrides: { - sensitivity_level?: "default" | "medium" | "low" | "eoff"; - action?: "block" | "managed_challenge" | "challenge" | "log"; // log = Enterprise Advanced only - categories?: Array<{ - category: string; // e.g., "http-flood", "udp-flood" - sensitivity_level?: string; - }>; - rules?: Array<{ - id: string; - action?: string; - sensitivity_level?: string; - }>; - }; - }; - }>; -} -``` - -## Expression Availability - -| Plan | Custom Expressions | Example | -|------|-------------------|---------| -| Free/Pro/Business | ✗ | Use `"true"` only | -| Enterprise | ✗ | Use `"true"` only | -| Enterprise Advanced | ✓ | `ip.src in {...}`, `http.request.uri.path matches "..."` | - -## Sensitivity Mapping - -| UI | API | Threshold | -|----|-----|-----------| -| High | `default` | Most aggressive | -| Medium | `medium` | Balanced | -| Low | `low` | Less aggressive | -| Essentially Off | `eoff` | Minimal mitigation | - -## Common Categories - -- `http-flood`, `http-anomaly` (L7) -- `udp-flood`, `syn-flood`, `dns-flood` (L3/4) - -## Override Precedence - -Multiple override layers apply in this order (higher precedence wins): - -``` -Zone-level > Account-level -Individual Rule > Category > Global sensitivity/action -``` - -**Example**: Zone rule for `/api/*` overrides account-level global settings. - -## Adaptive DDoS Profiles - -**Availability**: Enterprise, Enterprise Advanced -**Learning period**: 7 days of traffic history required - -| Profile Type | Description | Detects | -|--------------|-------------|---------| -| **Origins** | Traffic patterns per origin server | Anomalous requests to specific origins | -| **User-Agents** | Traffic patterns per User-Agent | Malicious/anomalous user agent strings | -| **Locations** | Traffic patterns per geo-location | Attacks from specific countries/regions | -| **Protocols** | Traffic patterns per protocol (L3/4) | Protocol-specific flood attacks | - -Configure by targeting specific adaptive rule IDs via API (see api.md#typed-override-examples). - -## Alerting - -Configure via Notifications: -- Alert types: `http_ddos_attack_alert`, `layer_3_4_ddos_attack_alert`, `advanced_*` variants -- Filters: zones, hostnames, RPS/PPS/Mbps thresholds, IPs, protocols -- Mechanisms: email, webhooks, PagerDuty - -See [api.md](./api.md#alert-configuration) for API examples. diff --git a/external/develop/devops/cloudflare/references/ddos/gotchas.md b/external/develop/devops/cloudflare/references/ddos/gotchas.md deleted file mode 100644 index f2a97d1..0000000 --- a/external/develop/devops/cloudflare/references/ddos/gotchas.md +++ /dev/null @@ -1,107 +0,0 @@ -# DDoS Gotchas - -## Common Errors - -### "False positives blocking legitimate traffic" - -**Cause**: Sensitivity too high, wrong action, or missing exceptions -**Solution**: -1. Lower sensitivity for specific rule/category -2. Use `log` action first to validate (Enterprise Advanced) -3. Add exception with custom expression (e.g., allowlist IPs) -4. Query flagged requests via GraphQL Analytics API to identify patterns - -### "Attacks getting through" - -**Cause**: Sensitivity too low or wrong action -**Solution**: Increase to `default` sensitivity and use `block` action: -```typescript -const config = { - rules: [{ - expression: "true", - action: "execute", - action_parameters: { id: managedRulesetId, overrides: { sensitivity_level: "default", action: "block" } }, - }], -}; -``` - -### "Adaptive rules not working" - -**Cause**: Insufficient traffic history (needs 7 days) -**Solution**: Wait for baseline to establish, check dashboard for adaptive rule status - -### "Zone override ignored" - -**Cause**: Account overrides conflict with zone overrides -**Solution**: Configure at zone level OR remove zone overrides to use account-level - -### "Log action not available" - -**Cause**: Not on Enterprise Advanced DDoS plan -**Solution**: Use `managed_challenge` with low sensitivity for testing - -### "Rule limit exceeded" - -**Cause**: Too many override rules (Free/Pro/Business: 1, Enterprise Advanced: 10) -**Solution**: Combine conditions in single expression using `and`/`or` - -### "Cannot override rule" - -**Cause**: Rule is read-only -**Solution**: Check API response for read-only indicator, use different rule - -### "Cannot disable DDoS protection" - -**Cause**: DDoS managed rulesets cannot be fully disabled (always-on protection) -**Solution**: Set `sensitivity_level: "eoff"` for minimal mitigation - -### "Expression not allowed" - -**Cause**: Custom expressions require Enterprise Advanced plan -**Solution**: Use `expression: "true"` for all traffic, or upgrade plan - -### "Managed ruleset not found" - -**Cause**: Zone/account doesn't have DDoS managed ruleset, or incorrect phase -**Solution**: Verify ruleset exists via `client.rulesets.list()`, check phase name (`ddos_l7` or `ddos_l4`) - -## API Error Codes - -| Error Code | Message | Cause | Solution | -|------------|---------|-------|----------| -| 10000 | Authentication error | Invalid/missing API token | Check token has DDoS permissions | -| 81000 | Ruleset validation failed | Invalid rule structure | Verify `action_parameters.id` is managed ruleset ID | -| 81020 | Expression not allowed | Custom expressions on wrong plan | Use `"true"` or upgrade to Enterprise Advanced | -| 81021 | Rule limit exceeded | Too many override rules | Reduce rules or upgrade (Enterprise Advanced: 10) | -| 81022 | Invalid sensitivity level | Wrong sensitivity value | Use: `default`, `medium`, `low`, `eoff` | -| 81023 | Invalid action | Wrong action for plan | Enterprise Advanced only: `log` action | - -## Limits - -| Resource/Limit | Free/Pro/Business | Enterprise | Enterprise Advanced | -|----------------|-------------------|------------|---------------------| -| Override rules per zone | 1 | 1 | 10 | -| Custom expressions | ✗ | ✗ | ✓ | -| Log action | ✗ | ✗ | ✓ | -| Adaptive DDoS | ✗ | ✓ | ✓ | -| Traffic history required | - | 7 days | 7 days | - -## Tuning Strategy - -1. Start with `log` action + `medium` sensitivity -2. Monitor for 24-48 hours -3. Identify false positives, add exceptions -4. Gradually increase to `default` sensitivity -5. Change action from `log` → `managed_challenge` → `block` -6. Document all adjustments - -## Best Practices - -- Test during low-traffic periods -- Use zone-level for per-site tuning -- Reference IP lists for easier management -- Set appropriate alert thresholds (avoid noise) -- Combine with WAF for layered defense -- Avoid over-tuning (keep config simple) - -See [patterns.md](./patterns.md) for progressive rollout examples. diff --git a/external/develop/devops/cloudflare/references/ddos/patterns.md b/external/develop/devops/cloudflare/references/ddos/patterns.md deleted file mode 100644 index a46ef2f..0000000 --- a/external/develop/devops/cloudflare/references/ddos/patterns.md +++ /dev/null @@ -1,174 +0,0 @@ -# DDoS Protection Patterns - -## Allowlist Trusted IPs - -```typescript -const config = { - description: "Allowlist trusted IPs", - rules: [{ - expression: "ip.src in { 203.0.113.0/24 192.0.2.1 }", - action: "execute", - action_parameters: { - id: managedRulesetId, - overrides: { sensitivity_level: "eoff" }, - }, - }], -}; - -await client.accounts.rulesets.phases.entrypoint.update("ddos_l7", { - account_id: accountId, - ...config, -}); -``` - -## Route-specific Sensitivity - -```typescript -const config = { - description: "Route-specific protection", - rules: [ - { - expression: "not http.request.uri.path matches \"^/api/\"", - action: "execute", - action_parameters: { - id: managedRulesetId, - overrides: { sensitivity_level: "default", action: "block" }, - }, - }, - { - expression: "http.request.uri.path matches \"^/api/\"", - action: "execute", - action_parameters: { - id: managedRulesetId, - overrides: { sensitivity_level: "low", action: "managed_challenge" }, - }, - }, - ], -}; -``` - -## Progressive Enhancement - -```typescript -enum ProtectionLevel { MONITORING = "monitoring", LOW = "low", MEDIUM = "medium", HIGH = "high" } - -const levelConfig = { - [ProtectionLevel.MONITORING]: { action: "log", sensitivity: "eoff" }, - [ProtectionLevel.LOW]: { action: "managed_challenge", sensitivity: "low" }, - [ProtectionLevel.MEDIUM]: { action: "managed_challenge", sensitivity: "medium" }, - [ProtectionLevel.HIGH]: { action: "block", sensitivity: "default" }, -} as const; - -async function setProtectionLevel(zoneId: string, level: ProtectionLevel, rulesetId: string, client: Cloudflare) { - const settings = levelConfig[level]; - return client.zones.rulesets.phases.entrypoint.update("ddos_l7", { - zone_id: zoneId, - rules: [{ - expression: "true", - action: "execute", - action_parameters: { id: rulesetId, overrides: { action: settings.action, sensitivity_level: settings.sensitivity } }, - }], - }); -} -``` - -## Dynamic Response to Attacks - -```typescript -interface Env { CLOUDFLARE_API_TOKEN: string; ZONE_ID: string; KV: KVNamespace; } - -export default { - async fetch(request: Request, env: Env): Promise { - if (request.url.includes("/attack-detected")) { - const attackData = await request.json(); - await env.KV.put(`attack:${Date.now()}`, JSON.stringify(attackData), { expirationTtl: 86400 }); - const recentAttacks = await getRecentAttacks(env.KV); - if (recentAttacks.length > 5) { - await setProtectionLevel(env.ZONE_ID, ProtectionLevel.HIGH, managedRulesetId, client); - return new Response("Protection increased"); - } - } - return new Response("OK"); - }, - async scheduled(event: ScheduledEvent, env: Env): Promise { - const recentAttacks = await getRecentAttacks(env.KV); - if (recentAttacks.length === 0) await setProtectionLevel(env.ZONE_ID, ProtectionLevel.MEDIUM, managedRulesetId, client); - }, -}; -``` - -## Multi-rule Tiered Protection (Enterprise Advanced) - -```typescript -const config = { - description: "Multi-tier DDoS protection", - rules: [ - { - expression: "not ip.src in $known_ips and not cf.bot_management.score gt 30", - action: "execute", - action_parameters: { id: managedRulesetId, overrides: { sensitivity_level: "default", action: "block" } }, - }, - { - expression: "cf.bot_management.verified_bot", - action: "execute", - action_parameters: { id: managedRulesetId, overrides: { sensitivity_level: "medium", action: "managed_challenge" } }, - }, - { - expression: "ip.src in $trusted_ips", - action: "execute", - action_parameters: { id: managedRulesetId, overrides: { sensitivity_level: "low" } }, - }, - ], -}; -``` - -## Defense in Depth - -Layered security stack: DDoS + WAF + Rate Limiting + Bot Management. - -```typescript -// Layer 1: DDoS (volumetric attacks) -await client.zones.rulesets.phases.entrypoint.update("ddos_l7", { - zone_id: zoneId, - rules: [{ expression: "true", action: "execute", action_parameters: { id: ddosRulesetId, overrides: { sensitivity_level: "medium" } } }], -}); - -// Layer 2: WAF (exploit protection) -await client.zones.rulesets.phases.entrypoint.update("http_request_firewall_managed", { - zone_id: zoneId, - rules: [{ expression: "true", action: "execute", action_parameters: { id: wafRulesetId } }], -}); - -// Layer 3: Rate Limiting (abuse prevention) -await client.zones.rulesets.phases.entrypoint.update("http_ratelimit", { - zone_id: zoneId, - rules: [{ expression: "http.request.uri.path eq \"/api/login\"", action: "block", ratelimit: { characteristics: ["ip.src"], period: 60, requests_per_period: 5 } }], -}); - -// Layer 4: Bot Management (automation detection) -await client.zones.rulesets.phases.entrypoint.update("http_request_sbfm", { - zone_id: zoneId, - rules: [{ expression: "cf.bot_management.score lt 30", action: "managed_challenge" }], -}); -``` - -## Cache Strategy for DDoS Mitigation - -Exclude query strings from cache key to counter randomized query parameter attacks. - -```typescript -const cacheRule = { - expression: "http.request.uri.path matches \"^/api/\"", - action: "set_cache_settings", - action_parameters: { - cache: true, - cache_key: { ignore_query_strings_order: true, custom_key: { query_string: { exclude: { all: true } } } }, - }, -}; - -await client.zones.rulesets.phases.entrypoint.update("http_request_cache_settings", { zone_id: zoneId, rules: [cacheRule] }); -``` - -**Rationale**: Attackers randomize query strings (`?random=123456`) to bypass cache. Excluding query params ensures cache hits absorb attack traffic. - -See [configuration.md](./configuration.md) for rule structure details. diff --git a/external/develop/devops/cloudflare/references/do-storage/README.md b/external/develop/devops/cloudflare/references/do-storage/README.md deleted file mode 100644 index 9f0542a..0000000 --- a/external/develop/devops/cloudflare/references/do-storage/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Cloudflare Durable Objects Storage - -Use SQLite for new classes. Existing KV-backed classes need their matching API reference; using key-value methods does not by itself identify the backend. - -Fetch the relevant current documentation before implementing or reviewing changes. - -| Task | Documentation | -|------|---------------| -| Choose SQL, key-value access, transactions, or recovery APIs | [SQLite storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/); [Legacy KV storage API](https://developers.cloudflare.com/durable-objects/api/legacy-kv-storage-api/) | -| Configure the backend, class lifecycle, and placement | [Configuration](configuration.md) | -| Find operation semantics and storage options | [API routing](api.md) | -| Design schemas, caches, scheduled work, or cleanup | [Patterns](patterns.md) | -| Diagnose concurrency, limits, and billing | [Troubleshooting](gotchas.md) | -| Verify storage behavior in the Workers runtime | [Testing](testing.md) | - -For object routing, WebSockets, and coordination design, see the [Durable Objects skill](../../../durable-objects/SKILL.md). diff --git a/external/develop/devops/cloudflare/references/do-storage/api.md b/external/develop/devops/cloudflare/references/do-storage/api.md deleted file mode 100644 index 90d5725..0000000 --- a/external/develop/devops/cloudflare/references/do-storage/api.md +++ /dev/null @@ -1,14 +0,0 @@ -# DO Storage API - -Check the class’s backend before choosing operations; storage APIs and recovery capabilities differ. - -Fetch the relevant current documentation before implementing or reviewing changes. - -| Task | Documentation | -|------|---------------| -| Use SQL cursors, bound parameters, supported SQL, or database size | [SQLite storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/) | -| Use synchronous or asynchronous key-value methods on SQLite | [SQLite storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/) | -| Maintain asynchronous KV operations on a legacy backend | [Legacy KV storage API](https://developers.cloudflare.com/durable-objects/api/legacy-kv-storage-api/) | -| Review transactions, write coalescing, storage options, or cleanup | [SQLite storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/); [Legacy KV storage API](https://developers.cloudflare.com/durable-objects/api/legacy-kv-storage-api/) | -| Create bookmarks or restore SQLite data | [SQLite storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/) | -| Schedule, inspect, or cancel an alarm | [Alarms](https://developers.cloudflare.com/durable-objects/api/alarms/) | diff --git a/external/develop/devops/cloudflare/references/do-storage/configuration.md b/external/develop/devops/cloudflare/references/do-storage/configuration.md deleted file mode 100644 index b356433..0000000 --- a/external/develop/devops/cloudflare/references/do-storage/configuration.md +++ /dev/null @@ -1,16 +0,0 @@ -# DO Storage Configuration - -Prefer SQLite for new classes. Inspect an existing class’s backend and lifecycle configuration before changing either. - -Fetch the relevant current documentation before implementing or reviewing changes. - -| Task | Documentation | -|------|---------------| -| Create a SQLite-backed class, binding, and generated types | [Getting started](https://developers.cloudflare.com/durable-objects/get-started/) | -| Choose storage and manage class exports | [Class exports](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/) | -| Maintain legacy migration configuration | [Legacy class migrations](https://developers.cloudflare.com/durable-objects/reference/durable-object-class-migrations-legacy/) | -| Initialize schemas or evolve application tables | [Rules of Durable Objects](https://developers.cloudflare.com/durable-objects/best-practices/rules-of-durable-objects/); [SQLite storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/) | -| Set placement hints or jurisdiction constraints | [Data location](https://developers.cloudflare.com/durable-objects/reference/data-location/) | -| Configure CPU allowances and check storage constraints | [Limits](https://developers.cloudflare.com/durable-objects/platform/limits/) | - -A class configuration change is not an application-data migration. Check the documented backend transition constraints in [Class exports](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/) before planning a backend change. diff --git a/external/develop/devops/cloudflare/references/do-storage/gotchas.md b/external/develop/devops/cloudflare/references/do-storage/gotchas.md deleted file mode 100644 index ab40a32..0000000 --- a/external/develop/devops/cloudflare/references/do-storage/gotchas.md +++ /dev/null @@ -1,15 +0,0 @@ -# DO Storage Troubleshooting - -Identify the backend and failing operation before applying concurrency or recovery guidance. - -Fetch the relevant current documentation before implementing or reviewing changes. - -| Task | Documentation | -|------|---------------| -| Input/output gates, write coalescing, external I/O races, or storage options | [Rules of Durable Objects](https://developers.cloudflare.com/durable-objects/best-practices/rules-of-durable-objects/); [SQLite storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/); [Legacy KV storage API](https://developers.cloudflare.com/durable-objects/api/legacy-kv-storage-api/) | -| SQL transactions, synchronous callbacks, parameter types, or numeric precision | [SQLite storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/) | -| Alarm cancellation and storage deletion | [Alarms](https://developers.cloudflare.com/durable-objects/api/alarms/); [SQLite storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/) | -| Slow queries, indexing, caching, or initialization | [Rules of Durable Objects](https://developers.cloudflare.com/durable-objects/best-practices/rules-of-durable-objects/); [Durable Object State](https://developers.cloudflare.com/durable-objects/api/state/) | -| Storage limits or CPU exhaustion | [Limits](https://developers.cloudflare.com/durable-objects/platform/limits/) | -| Storage charges and operation accounting | [Pricing](https://developers.cloudflare.com/durable-objects/platform/pricing/) | -| Overload, storage timeouts, or object resets | [Troubleshooting](https://developers.cloudflare.com/durable-objects/observability/troubleshooting/); [Error handling](https://developers.cloudflare.com/durable-objects/best-practices/error-handling/) | diff --git a/external/develop/devops/cloudflare/references/do-storage/patterns.md b/external/develop/devops/cloudflare/references/do-storage/patterns.md deleted file mode 100644 index 76ffd9b..0000000 --- a/external/develop/devops/cloudflare/references/do-storage/patterns.md +++ /dev/null @@ -1,15 +0,0 @@ -# DO Storage Patterns - -Persist essential state and treat memory as a reconstructible cache. Coordinate related updates within the storage and concurrency guarantees of the selected backend. - -Fetch the relevant current documentation before implementing or reviewing changes. - -| Task | Documentation | -|------|---------------| -| Schema initialization, migrations, indexes, caching, or parent-child coordination | [Rules of Durable Objects](https://developers.cloudflare.com/durable-objects/best-practices/rules-of-durable-objects/) | -| Counters, transactions, and atomic updates | [SQLite storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/); [Legacy KV storage API](https://developers.cloudflare.com/durable-objects/api/legacy-kv-storage-api/); [Counter example](https://developers.cloudflare.com/durable-objects/examples/build-a-counter/) | -| Batch processing or multiple scheduled events | [Alarms](https://developers.cloudflare.com/durable-objects/api/alarms/); [Batching example](https://developers.cloudflare.com/durable-objects/examples/alarms-api/) | -| Cleanup and expiration | [Time to Live example](https://developers.cloudflare.com/durable-objects/examples/durable-object-ttl/); [SQLite storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/) | -| Design application-specific rate limiting | [Rules of Durable Objects](https://developers.cloudflare.com/durable-objects/best-practices/rules-of-durable-objects/); [SQLite storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/) | - -Verify persistence, isolation, and rollback behavior with the [testing guidance](testing.md). diff --git a/external/develop/devops/cloudflare/references/do-storage/testing.md b/external/develop/devops/cloudflare/references/do-storage/testing.md deleted file mode 100644 index 9eeb8fe..0000000 --- a/external/develop/devops/cloudflare/references/do-storage/testing.md +++ /dev/null @@ -1,13 +0,0 @@ -# DO Storage Testing - -Choose tests around persistence, rollback, instance isolation, and scheduled-work behavior. Inspect installed test packages and configuration before changing the suite. - -Fetch the relevant current documentation before implementing or reviewing changes. - -| Task | Documentation | -|------|---------------| -| Set up or migrate a test suite, choose helpers, and manage isolation | [Testing Durable Objects](../../../durable-objects/references/testing.md) | -| Exercise RPC, SQLite storage, and alarms | [Testing Durable Objects example](https://developers.cloudflare.com/durable-objects/examples/testing-with-durable-objects/) | -| Determine the storage or recovery contract to verify | [SQLite storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/); [Legacy KV storage API](https://developers.cloudflare.com/durable-objects/api/legacy-kv-storage-api/) | - -Use the current test documentation for helper signatures and runtime limitations. For point-in-time recovery tests, check both the storage API and the test runtime’s supported behavior before assuming a restart reproduces production recovery. diff --git a/external/develop/devops/cloudflare/references/email-routing/README.md b/external/develop/devops/cloudflare/references/email-routing/README.md deleted file mode 100644 index 4ff4255..0000000 --- a/external/develop/devops/cloudflare/references/email-routing/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Email Routing - -Use routing rules for address-based forwarding; use an Email Worker when incoming mail needs custom processing. Fetch the linked docs before implementing APIs, DNS, configuration, or limits. - -| Task | Start here | -| --- | --- | -| Forward incoming mail to an existing mailbox | [Route emails](https://developers.cloudflare.com/email-service/get-started/route-emails/) | -| Manage addresses, verification, catch-all rules, or subaddressing | [Routing rules and addresses](https://developers.cloudflare.com/email-service/configuration/email-routing-addresses/) | -| Filter, parse, reply to, or store incoming mail | [Email Workers](../email-workers/README.md) | -| Send a new outbound message | [Send emails](https://developers.cloudflare.com/email-service/get-started/send-emails/) — Workers binding, REST API, or SMTP | - -Forwarding requires verified destinations. Replying within an incoming email event and sending a new outbound message have different requirements; use the relevant API docs. - -## Reference map - -- [Configuration](configuration.md): domains, rules, deployment, and local testing. -- [API](api.md): routing management and inbound/outbound operations. -- [Patterns](patterns.md): filtering, parsing, storage, and notifications. -- [Troubleshooting](gotchas.md): authentication, delivery, and current limits. diff --git a/external/develop/devops/cloudflare/references/email-routing/api.md b/external/develop/devops/cloudflare/references/email-routing/api.md deleted file mode 100644 index c073c52..0000000 --- a/external/develop/devops/cloudflare/references/email-routing/api.md +++ /dev/null @@ -1,13 +0,0 @@ -# Email Routing APIs - -Fetch the relevant API page before writing code; do not infer sending types or recipient restrictions from incoming-mail APIs. - -| Task | Documentation | -| --- | --- | -| Manage routing settings, rules, and destination addresses programmatically | [Email Routing REST API](https://developers.cloudflare.com/api/resources/email_routing/) | -| Read incoming message metadata; forward, reply, or reject | [Email handler API](https://developers.cloudflare.com/email-service/api/route-emails/email-handler/) | -| Send from a Worker, including attachments or existing raw MIME | [Sending Workers API](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/) | -| Restrict a sending binding's senders or recipients | [Configure send bindings](https://developers.cloudflare.com/email-service/configuration/send-bindings/) | -| Send from an external application | [Sending REST API](https://developers.cloudflare.com/email-service/api/send-emails/rest-api/) or [SMTP](https://developers.cloudflare.com/email-service/api/send-emails/smtp/) | - -For incoming messages, distinguish SMTP envelope addresses from message headers. Use [Email Workers API guidance](../email-workers/api.md) for processing and [authentication docs](https://developers.cloudflare.com/email-service/concepts/email-authentication/) for identity checks. diff --git a/external/develop/devops/cloudflare/references/email-routing/configuration.md b/external/develop/devops/cloudflare/references/email-routing/configuration.md deleted file mode 100644 index 734ba1e..0000000 --- a/external/develop/devops/cloudflare/references/email-routing/configuration.md +++ /dev/null @@ -1,13 +0,0 @@ -# Email Routing Setup - -Fetch the setup page matching the operation. Email Sending and Email Routing have separate domain configuration; enabling one is not a substitute for configuring the other. - -| Task | Documentation | -| --- | --- | -| Onboard a routing domain and deploy/connect an Email Worker | [Route emails](https://developers.cloudflare.com/email-service/get-started/route-emails/) | -| Inspect DNS records, conflicts, verification, or disable routing | [Domain configuration](https://developers.cloudflare.com/email-service/configuration/domains/) | -| Verify forwarding destinations and manage routing or catch-all rules | [Routing rules and addresses](https://developers.cloudflare.com/email-service/configuration/email-routing-addresses/) | -| Configure a subdomain | [Subdomains](https://developers.cloudflare.com/email-service/configuration/subdomains/) | -| Configure outbound email | [Send emails](https://developers.cloudflare.com/email-service/get-started/send-emails/) and [send binding restrictions](https://developers.cloudflare.com/email-service/configuration/send-bindings/) | -| Test an incoming email locally | [Local routing development](https://developers.cloudflare.com/email-service/local-development/routing/) | -| Add Worker storage, types, secrets, or environments | [Email Workers configuration](../email-workers/configuration.md) | diff --git a/external/develop/devops/cloudflare/references/email-routing/gotchas.md b/external/develop/devops/cloudflare/references/email-routing/gotchas.md deleted file mode 100644 index 5dbf2ba..0000000 --- a/external/develop/devops/cloudflare/references/email-routing/gotchas.md +++ /dev/null @@ -1,15 +0,0 @@ -# Email Routing Troubleshooting - -Start with the message's activity log to distinguish routing, authentication, and delivery failures, then fetch the matching documentation. - -| Symptom or question | Documentation | -| --- | --- | -| Rule disabled, wrong destination, or catch-all behavior | [Routing rules and verified addresses](https://developers.cloudflare.com/email-service/configuration/email-routing-addresses/) | -| DNS conflict or domain not configured | [Domain configuration](https://developers.cloudflare.com/email-service/configuration/domains/) | -| SPF, DKIM, or DMARC failure | [Authentication troubleshooting](https://developers.cloudflare.com/email-service/reference/troubleshooting/) | -| Message missing, rejected, dropped, or delivery failed | [Email logs](https://developers.cloudflare.com/email-service/observability/logs/) | -| Quotas, message sizes, routing capacity, or Worker resource exhaustion | [Current limits](https://developers.cloudflare.com/email-service/platform/limits/) | -| Sending costs and verified-destination allowances | [Pricing](https://developers.cloudflare.com/email-service/platform/pricing/) | -| Stream, parser, reply, or Worker execution error | [Email Workers troubleshooting](../email-workers/gotchas.md) | - -Do not use a sender-address string as proof of authentication. Inspect the authentication results described in the logs and authentication docs. diff --git a/external/develop/devops/cloudflare/references/email-routing/patterns.md b/external/develop/devops/cloudflare/references/email-routing/patterns.md deleted file mode 100644 index 38b6e9b..0000000 --- a/external/develop/devops/cloudflare/references/email-routing/patterns.md +++ /dev/null @@ -1,13 +0,0 @@ -# Email Routing Patterns - -Prefer a routing rule when the destination depends only on the email address. Use an Email Worker for decisions based on message content or application state. - -| Task | Documentation | -| --- | --- | -| Address-based forwarding, catch-all, or subaddressing | [Routing rules and addresses](https://developers.cloudflare.com/email-service/configuration/email-routing-addresses/) | -| Recipient/subject routing, multiple destinations, rejection, or automatic replies | [Email handler actions](https://developers.cloudflare.com/email-service/api/route-emails/email-handler/) | -| Filter unwanted messages | [Spam filtering](https://developers.cloudflare.com/email-service/examples/email-routing/spam-filtering/) | -| Parse MIME, extract attachments, archive mail, or notify an application | [Email Workers patterns](../email-workers/patterns.md) | -| Send outbound attachments | [Email attachments](https://developers.cloudflare.com/email-service/examples/email-sending/email-attachments/) | - -Verify all forwarding destinations. For delayed responses after processing or human review, use the outbound sending API; an incoming event's reply operation belongs to that event. diff --git a/external/develop/devops/cloudflare/references/email-workers/README.md b/external/develop/devops/cloudflare/references/email-workers/README.md deleted file mode 100644 index 8d29a5e..0000000 --- a/external/develop/devops/cloudflare/references/email-workers/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Email Workers - -Use an Email Worker's `email()` handler for custom processing of incoming mail. Use [routing rules](../email-routing/README.md) for simple address-based forwarding. Fetch current documentation before implementing the handler or its dependencies. - -| Operation | Documentation | -| --- | --- | -| Forward to a verified destination, reject, or reply within the incoming event | [Email handler API](https://developers.cloudflare.com/email-service/api/route-emails/email-handler/) | -| Send a new message or a later response | [Sending Workers API](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/) | -| Parse and store mail for later processing | [Email storage and processing](https://developers.cloudflare.com/email-service/examples/email-routing/email-storage/) | - -`message.raw` is a single-use stream. If parsing and archiving both need the raw content, plan how to reuse it rather than reading the stream twice. Forwarding destinations must be verified; reply requirements are documented separately from outbound sending. - -## Reference map - -- [Configuration](configuration.md): routing, bindings, local development, and types. -- [API](api.md): message actions, MIME, and sending. -- [Patterns](patterns.md): filtering, storage, attachments, and background processing. -- [Troubleshooting](gotchas.md): stream handling, authentication, limits, and errors. diff --git a/external/develop/devops/cloudflare/references/email-workers/api.md b/external/develop/devops/cloudflare/references/email-workers/api.md deleted file mode 100644 index ea019be..0000000 --- a/external/develop/devops/cloudflare/references/email-workers/api.md +++ /dev/null @@ -1,15 +0,0 @@ -# Email Workers APIs - -Fetch the relevant page for current interfaces and return types. - -| Task | Documentation | -| --- | --- | -| Implement the handler; inspect envelope addresses, headers, raw content, or size | [Email handler API](https://developers.cloudflare.com/email-service/api/route-emails/email-handler/) | -| Forward, add forwarding headers, reject, or reply with MIME and threading | [Email actions and reply requirements](https://developers.cloudflare.com/email-service/api/route-emails/email-handler/) | -| Parse MIME bodies and attachments | [Email handler parsing guidance](https://developers.cloudflare.com/email-service/api/route-emails/email-handler/) — follow its postal-mime reference | -| Send new outbound mail or an existing raw MIME message | [Sending Workers API](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/) | -| Configure sender and recipient restrictions | [Send bindings](https://developers.cloudflare.com/email-service/configuration/send-bindings/) | -| Set outbound headers | [Email headers](https://developers.cloudflare.com/email-service/reference/headers/) | -| Generate Worker and binding types | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/) | - -Envelope addresses describe SMTP transport; message headers describe the message. Neither an address comparison nor a display header replaces [email authentication](https://developers.cloudflare.com/email-service/concepts/email-authentication/). diff --git a/external/develop/devops/cloudflare/references/email-workers/configuration.md b/external/develop/devops/cloudflare/references/email-workers/configuration.md deleted file mode 100644 index c820937..0000000 --- a/external/develop/devops/cloudflare/references/email-workers/configuration.md +++ /dev/null @@ -1,16 +0,0 @@ -# Email Workers Configuration - -An incoming routing rule connects an address to a Worker. Add an outbound sending binding when the application needs the sending API. - -| Task | Documentation | -| --- | --- | -| Create, deploy, and connect an email-processing Worker | [Route emails](https://developers.cloudflare.com/email-service/get-started/route-emails/) | -| Verify destinations, configure rules, or check DNS | [Email Routing configuration](../email-routing/configuration.md) | -| Configure outbound sending and address restrictions | [Sending Workers API](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/) and [send bindings](https://developers.cloudflare.com/email-service/configuration/send-bindings/) | -| Simulate incoming messages | [Local routing development](https://developers.cloudflare.com/email-service/local-development/routing/) | -| Test outbound messages and attachment behavior | [Local sending development](https://developers.cloudflare.com/email-service/local-development/sending/) | -| Configure KV, R2, D1, variables, or environments | [Wrangler configuration](https://developers.cloudflare.com/workers/wrangler/configuration/) | -| Generate runtime and binding types | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/) | -| Store credentials | [Workers secrets](https://developers.cloudflare.com/workers/configuration/secrets/) | - -Follow the handler docs for MIME library requirements. Local sending simulation and remote sending have different effects: remote bindings deliver real email. diff --git a/external/develop/devops/cloudflare/references/email-workers/gotchas.md b/external/develop/devops/cloudflare/references/email-workers/gotchas.md deleted file mode 100644 index 1db7530..0000000 --- a/external/develop/devops/cloudflare/references/email-workers/gotchas.md +++ /dev/null @@ -1,15 +0,0 @@ -# Email Workers Troubleshooting - -| Symptom or decision | Documentation | -| --- | --- | -| Raw stream already consumed or locked | [ReadableStream](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestream/) and [handler parsing guidance](https://developers.cloudflare.com/email-service/api/route-emails/email-handler/) | -| Forwarding or reply exception; unsupported forwarding headers | [Email handler actions and requirements](https://developers.cloudflare.com/email-service/api/route-emails/email-handler/) | -| Unverified destination or disabled rule | [Routing rules and addresses](https://developers.cloudflare.com/email-service/configuration/email-routing-addresses/) | -| Sender identity or authentication failure | [Email authentication](https://developers.cloudflare.com/email-service/concepts/email-authentication/) and [troubleshooting](https://developers.cloudflare.com/email-service/reference/troubleshooting/) | -| Sending validation, attachment, or recipient error | [Sending API errors](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/) | -| Local test or binary attachment issue | [Local routing](https://developers.cloudflare.com/email-service/local-development/routing/) and [local sending](https://developers.cloudflare.com/email-service/local-development/sending/) | -| CPU, memory, message-size, or reply limits | [Email limits](https://developers.cloudflare.com/email-service/platform/limits/) and [Workers limits](https://developers.cloudflare.com/workers/platform/limits/) | -| Background work or unhandled error | [Execution context](https://developers.cloudflare.com/workers/runtime-apis/context/) and [Workers logs](https://developers.cloudflare.com/workers/observability/logs/) | -| Mail accepted but missing at the destination | [Email activity logs](https://developers.cloudflare.com/email-service/observability/logs/) | - -Raw content is single-use: reuse buffered content if multiple operations need it, and account for memory limits. `waitUntil()` extends execution lifetime; it does not remove CPU or memory limits. Diagnose reply failures against the incoming message's requirements, not just the sending domain's DNS. diff --git a/external/develop/devops/cloudflare/references/email-workers/patterns.md b/external/develop/devops/cloudflare/references/email-workers/patterns.md deleted file mode 100644 index 66b6ec7..0000000 --- a/external/develop/devops/cloudflare/references/email-workers/patterns.md +++ /dev/null @@ -1,16 +0,0 @@ -# Email Workers Patterns - -Fetch the workflow page, then adapt it to the application's routing and storage requirements. - -| Task | Documentation | -| --- | --- | -| Route by recipient or subject, forward to multiple destinations, or reject | [Email handler actions](https://developers.cloudflare.com/email-service/api/route-emails/email-handler/) | -| Parse MIME bodies and attachments | [Email handler parsing guidance](https://developers.cloudflare.com/email-service/api/route-emails/email-handler/) | -| Filter incoming mail | [Spam filtering](https://developers.cloudflare.com/email-service/examples/email-routing/spam-filtering/) | -| Reply within the incoming event with threading | [Reply requirements and examples](https://developers.cloudflare.com/email-service/api/route-emails/email-handler/) | -| Archive metadata in KV or enqueue mail for later processing | [Email storage and processing](https://developers.cloudflare.com/email-service/examples/email-routing/email-storage/) | -| Store raw mail or extracted attachment bytes in R2 | [R2 Workers API](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/) | -| Notify a webhook or schedule work within the invocation lifetime | [Fetch](https://developers.cloudflare.com/workers/runtime-apis/fetch/) and [execution context](https://developers.cloudflare.com/workers/runtime-apis/context/) | -| Send a later response or new outbound attachment | [Sending Workers API](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/) and [attachment examples](https://developers.cloudflare.com/email-service/examples/email-sending/email-attachments/) | - -Plan a single read of raw content when both parsing and storage need it. A queue consumer or later request sends through the outbound API because the original incoming email event is no longer available. diff --git a/external/develop/devops/cloudflare/references/flagship/README.md b/external/develop/devops/cloudflare/references/flagship/README.md deleted file mode 100644 index b877f6d..0000000 --- a/external/develop/devops/cloudflare/references/flagship/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Cloudflare Flagship - -Feature flag service for controlling feature visibility without redeploying code. Define flags with targeting rules and percentage-based rollouts, then evaluate them in Workers via a native binding or from any JavaScript runtime via the OpenFeature SDK. - -## When to Use - -| Need | Use Flagship? | Alternative | -|------|--------------|-------------| -| Feature toggles (on/off) | Yes | — | -| Gradual rollouts (percentage-based) | Yes | — | -| A/B testing with attribute targeting | Yes | — | -| Multi-variant configuration delivery | Yes | — | -| Environment-specific config (dev/staging/prod) | Consider | Wrangler environments, secrets | -| Static config that never changes | No | `wrangler.jsonc` vars | -| Per-request rate limiting | No | Rate Limiting rules | - -## Key Concepts - -- **Apps** — Top-level organizational unit. Maps to a project or service. Each account can have multiple apps. -- **Flags** — Named feature toggles with a key, variations, targeting rules, and enabled/disabled state. -- **Variations** — Possible values a flag returns. Types: boolean, string, number, JSON object. All variations on a flag must share the same type. -- **Targeting rules** — Sequential, priority-ordered conditions that determine which variation to serve. First match wins; no match returns the default. -- **Evaluation context** — Key-value attributes (`userId`, `country`, `plan`, etc.) passed at evaluation time for rule matching and rollout bucketing. -- **Percentage rollouts** — Gradually release to a fraction of users. Consistent hashing on a configurable attribute ensures sticky bucketing. - -## Two Evaluation Paths - -| Path | Runtime | Package | Latency | Auth | -|------|---------|---------|---------|------| -| **Binding** (`env.FLAGS`) | Workers only | `@cloudflare/workers-types` | Lowest (no HTTP) | Automatic via binding | -| **OpenFeature SDK** | Workers, Node.js, browser | `@cloudflare/flagship` + `@openfeature/server-sdk` or `@openfeature/web-sdk` | HTTP per eval (server) or prefetch (client) | API token or binding passthrough | - -**Recommendation:** Use the binding inside Workers. Use the SDK when running outside Workers or when you need OpenFeature vendor-neutrality. - -## Reading Order - -| Task | Read | -|------|------| -| Set up Flagship in a Worker | `configuration.md` → `api.md` | -| Evaluate flags in code | `configuration.md` → `patterns.md` | -| Manage flags via REST API | `api.md` → `patterns.md` | -| Design targeting rules & rollouts | `patterns.md` → `gotchas.md` | -| Debug flag evaluation issues | `gotchas.md` → `api.md` | - -REST API note: management endpoints use Cloudflare v4 envelopes (`result`, `result_info`, `errors`) and snake_case fields. The `/evaluate` endpoint is the exception: it is not enveloped and returns OpenFeature-style camelCase. - -## In This Reference - -- **[api.md](./api.md)** — REST API endpoints, binding methods, OpenFeature SDK, schemas -- **[configuration.md](./configuration.md)** — Wrangler binding setup, SDK installation, TypeScript types -- **[patterns.md](./patterns.md)** — Flag CRUD via API, targeting rules, rollouts, OpenFeature usage -- **[gotchas.md](./gotchas.md)** — Common errors, limits, anti-patterns, troubleshooting - -## See Also - -- **[Flagship API reference](https://developers.cloudflare.com/api/resources/flagship/)** — Source of truth for REST API paths, envelopes, and response fields -- **[Workers docs](https://developers.cloudflare.com/workers/)** — Workers runtime (Flagship runs inside Workers) -- **[../kv/](../kv/)** — KV storage (Flagship uses KV infrastructure for flag delivery) -- **[Wrangler docs](https://developers.cloudflare.com/workers/wrangler/)** — Wrangler CLI for deployment and config diff --git a/external/develop/devops/cloudflare/references/flagship/api.md b/external/develop/devops/cloudflare/references/flagship/api.md deleted file mode 100644 index a9d05e1..0000000 --- a/external/develop/devops/cloudflare/references/flagship/api.md +++ /dev/null @@ -1,390 +0,0 @@ -# Flagship API Reference - -## Binding API (Workers) - -The binding is available as `env.FLAGS` (type `Flagship` from `@cloudflare/workers-types`). - -### Evaluation Methods - -All methods are async, never throw, and return the `defaultValue` on errors. - -| Method | Signature | Returns | -|--------|-----------|---------| -| `get` | `get(flagKey, defaultValue?, context?)` | `Promise` | -| `getBooleanValue` | `getBooleanValue(flagKey, defaultValue, context?)` | `Promise` | -| `getStringValue` | `getStringValue(flagKey, defaultValue, context?)` | `Promise` | -| `getNumberValue` | `getNumberValue(flagKey, defaultValue, context?)` | `Promise` | -| `getObjectValue` | `getObjectValue(flagKey, defaultValue, context?)` | `Promise` | -| `getBooleanDetails` | `getBooleanDetails(flagKey, defaultValue, context?)` | `Promise>` | -| `getStringDetails` | `getStringDetails(flagKey, defaultValue, context?)` | `Promise>` | -| `getNumberDetails` | `getNumberDetails(flagKey, defaultValue, context?)` | `Promise>` | -| `getObjectDetails` | `getObjectDetails(flagKey, defaultValue, context?)` | `Promise>` | - -### Parameters (shared across all methods) - -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `flagKey` | `string` | Yes | Flag key to evaluate | -| `defaultValue` | varies | Yes (except `get`) | Fallback if evaluation fails or flag not found | -| `context` | `FlagshipEvaluationContext` | No | Attributes for targeting rules (`{ userId: "user-42", country: "US" }`) | - -### Types - -```typescript -type FlagshipEvaluationContext = Record; - -interface FlagshipEvaluationDetails { - flagKey: string; - value: T; - variant?: string; // name of the matched variation - reason?: string; // "TARGETING_MATCH" | "DEFAULT" | "DISABLED" | "SPLIT" - errorCode?: string; // "TYPE_MISMATCH" | "GENERAL" - errorMessage?: string; -} -``` - -### Example - -```typescript -export default { - async fetch(request: Request, env: Env): Promise { - const enabled = await env.FLAGS.getBooleanValue("new-feature", false, { - userId: "user-42", - }); - return new Response(enabled ? "Feature on" : "Feature off"); - }, -}; -``` - ---- - -## OpenFeature SDK - -Package: `@cloudflare/flagship` - -### Server Provider (`FlagshipServerProvider`) - -For Workers, Node.js, and server-side JavaScript. - -**With binding (recommended inside Workers):** - -```typescript -import { OpenFeature } from "@openfeature/server-sdk"; -import { FlagshipServerProvider } from "@cloudflare/flagship"; - -await OpenFeature.setProviderAndWait( - new FlagshipServerProvider({ binding: env.FLAGS }), -); -const client = OpenFeature.getClient(); -const enabled = await client.getBooleanValue("new-checkout", false, { - targetingKey: "user-42", -}); -``` - -**With app ID (Node.js / non-Worker runtimes):** - -```typescript -import { OpenFeature } from "@openfeature/server-sdk"; -import { FlagshipServerProvider } from "@cloudflare/flagship"; - -await OpenFeature.setProviderAndWait( - new FlagshipServerProvider({ - appId: "", - accountId: "", - authToken: "", - }), -); -const client = OpenFeature.getClient(); -const enabled = await client.getBooleanValue("new-checkout", false, { - targetingKey: "user-42", -}); -``` - -### Client Provider (`FlagshipClientProvider`) - -For browser applications. Pre-fetches flags on init, evaluates synchronously. - -```typescript -import { OpenFeature } from "@openfeature/web-sdk"; -import { FlagshipClientProvider } from "@cloudflare/flagship"; - -await OpenFeature.setProviderAndWait( - new FlagshipClientProvider({ - appId: "", - accountId: "", - authToken: "", - prefetchFlags: ["promo-banner", "dark-mode"], - }), -); -await OpenFeature.setContext({ targetingKey: "user-42", plan: "enterprise" }); -const client = OpenFeature.getClient(); - -// Synchronous — no await needed -const showBanner = client.getBooleanValue("promo-banner", false); -``` - -**Important:** Only flags listed in `prefetchFlags` are available. Unlisted flags return `FLAG_NOT_FOUND`. - -### SDK Hooks - -```typescript -import { LoggingHook, TelemetryHook } from "@cloudflare/flagship"; -OpenFeature.addHooks(new LoggingHook(), new TelemetryHook()); -``` - ---- - -## REST API (Flag Management) - -Source of truth: [Cloudflare Flagship API reference](https://developers.cloudflare.com/api/resources/flagship/). Use it to verify REST paths, envelopes, response fields, and permission wording before relying on examples here. - -### FIRST: Check Prerequisites - -Before making any REST API calls (create, read, update, delete, toggle flags), verify these environment variables are set: - -| Variable | Purpose | How to get | -|----------|---------|------------| -| `CLOUDFLARE_ACCOUNT_ID` | Account identifier | Dashboard URL or `wrangler whoami` | -| `CLOUDFLARE_API_TOKEN` | Bearer token for API auth | [Create API token](https://dash.cloudflare.com/profile/api-tokens) with Flagship permissions | -| `FLAGSHIP_APP_ID` | Target app UUID | Dashboard under **Compute > Flagship**, or `GET /apps` endpoint | - -Check with: - -```bash -echo "CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID:-(not set)}" -echo "CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN:-(not set)}" -echo "FLAGSHIP_APP_ID=${FLAGSHIP_APP_ID:-(not set)}" -``` - -**If any are missing, ask the user to provide them before proceeding.** - -### Base URL and Auth - -Base URL: `https://api.cloudflare.com/client/v4/accounts/{account_id}/flagship` - -Authentication: `Authorization: Bearer ` - -Management endpoints use the Cloudflare v4 envelope. On success, the payload is under `result`; errors are an array under `errors`. - -```jsonc -// Success -{ "success": true, "result": , "errors": [], "messages": [] } - -// Paginated success -{ - "success": true, - "result": [], - "result_info": { "count": 50, "cursor": "next-cursor-or-null" }, - "errors": [], - "messages": [] -} - -// Error -{ "success": false, "result": null, "errors": [{ "message": "message" }], "messages": [] } -``` - -### App Endpoints - -| Method | Path | Description | -|--------|------|-------------| -| `GET` | `/apps` | List all apps | -| `GET` | `/apps/{app_id}` | Get app | -| `POST` | `/apps` | Create app (`{ "name": "my-app" }`) | -| `PUT` | `/apps/{app_id}` | Update app (`{ "name": "new-name" }`) | -| `DELETE` | `/apps/{app_id}` | Delete app | - -App name constraints: alphanumeric + hyphens + underscores, 1-64 chars. - -### Flag Endpoints - -| Method | Path | Description | -|--------|------|-------------| -| `GET` | `/apps/{app_id}/flags?limit=50&cursor=` | List flags (paginated) | -| `GET` | `/apps/{app_id}/flags/{flag_key}` | Get flag | -| `POST` | `/apps/{app_id}/flags` | Create flag | -| `PUT` | `/apps/{app_id}/flags/{flag_key}` | Update flag (full replace) | -| `DELETE` | `/apps/{app_id}/flags/{flag_key}` | Delete flag | -| `GET` | `/apps/{app_id}/flags/{flag_key}/changelog?limit=20&cursor=` | Flag changelog | - -### Evaluate Endpoint - -``` -GET /apps/{app_id}/evaluate?flagKey=& -``` - -Requires an API token with the `com.cloudflare.account.flagship.evaluate` permission. Context attributes passed as query params. This endpoint is not wrapped in the management envelope; the SDK contract returns OpenFeature-style camelCase: - -```json -{ - "flagKey": "my-flag", - "value": true, - "variant": "on", - "reason": "SPLIT" -} -``` - -Reasons: `TARGETING_MATCH`, `SPLIT`, `DEFAULT`, `DISABLED`. - -### Management Response Payloads - -Management endpoints are wrapped in the Cloudflare v4 envelope shown above. Common `.result` payloads: - -**App result** - -```json -{ - "id": "app-uuid", - "name": "my-app", - "created_at": "2026-06-09T12:00:00.000Z", - "updated_at": "2026-06-09T12:00:00.000Z", - "updated_by": "user@example.com" -} -``` - -**Flag result** - -```json -{ - "key": "my-flag", - "type": "boolean", - "default_variation": "off", - "variations": { "on": true, "off": false }, - "rules": [], - "description": "Enables the new feature", - "enabled": true, - "updated_at": "2026-06-09T12:00:00.000Z", - "updated_by": "user@example.com" -} -``` - -**Changelog entry** - -```json -{ - "flag_key": "my-flag", - "event": "update", - "after": { "key": "my-flag", "default_variation": "off", "variations": { "on": true, "off": false }, "rules": [], "enabled": true }, - "diff": { "enabled": { "from": false, "to": true } } -} -``` - -Changelog entries include the full flag state after the change. `update` entries also include `diff`. - ---- - -## FlagDefinition Schema - -```json -{ - "key": "my-flag", - "type": "boolean", - "default_variation": "off", - "variations": { - "on": true, - "off": false - }, - "rules": [ - { - "priority": 1, - "conditions": [ - { - "attribute": "email", - "operator": "ends_with", - "value": "@cloudflare.com" - } - ], - "serve_variation": "on", - "rollout": { "percentage": 100 } - } - ], - "description": "Enables the new feature", - "enabled": true -} -``` - -### Field Constraints - -| Field | Type | Constraints | -|-------|------|-------------| -| `key` | string | 1-64 chars, `/^[a-zA-Z0-9_-]+$/` | -| `type` | enum | Optional. `boolean`, `string`, `number`, `json` (auto-inferred from variations) | -| `default_variation` | string | Must be a key in `variations` | -| `variations` | `Record` | At least one. All values same type. Keys: alphanumeric/hyphens/underscores, max 64 chars. Values max 10KB. | -| `rules` | `Rule[]` | Can be empty. No duplicate priorities. | -| `description` | string? | Max 512 chars, nullable | -| `enabled` | boolean | Required. `false` = always returns default variation. | - -### Rule Schema - -```json -{ - "priority": 1, - "conditions": [ /* Condition[] */ ], - "serve_variation": "on", - "rollout": { "percentage": 50, "attribute": "targetingKey" } -} -``` - -- `priority`: integer >= 1, unique across rules in the flag (lower = evaluated first) -- `conditions`: array of base or logical conditions -- `serve_variation`: must be a key in `variations` -- `rollout`: optional. `percentage` 0-100. `attribute` defaults to `targetingKey`. - -### Condition Schema - -**Base condition:** - -```json -{ "attribute": "email", "operator": "ends_with", "value": "@cloudflare.com" } -``` - -**Logical condition (AND/OR):** - -```json -{ - "logical_operator": "AND", - "clauses": [ - { "attribute": "country", "operator": "equals", "value": "US" }, - { "attribute": "plan", "operator": "in", "value": ["enterprise", "business"] } - ] -} -``` - -Nesting supported up to 6 levels deep. - -### Operators - -| Operator | Description | Value Type | -|----------|-------------|------------| -| `equals` | Exact match (case-sensitive) | String | -| `not_equals` | Not exact match | String | -| `greater_than` | Numeric / datetime > | Number, ISO 8601 | -| `less_than` | Numeric / datetime < | Number, ISO 8601 | -| `greater_than_or_equals` | >= | Number, ISO 8601 | -| `less_than_or_equals` | <= | Number, ISO 8601 | -| `contains` | Substring match (case-sensitive) | String | -| `starts_with` | Prefix match | String | -| `ends_with` | Suffix match | String | -| `in` | Value in array | Array | -| `not_in` | Value not in array | Array | - ---- - -## Rate Limits - -| Operation | Limit | -|-----------|-------| -| Mutations (POST/PUT/DELETE) | 60 per 60s per account:app | -| Reads (GET) | 600 per 60s per account:app | - -## Error Codes - -| HTTP Status | Meaning | -|-------------|---------| -| 200 | Success (read/update/delete) | -| 201 | Created (create) | -| 400 | Validation error (check `errors[].message`) | -| 401 | Invalid or missing token | -| 404 | Flag or app not found | -| 409 | Flag key already exists (create) | -| 429 | Rate limited | diff --git a/external/develop/devops/cloudflare/references/flagship/configuration.md b/external/develop/devops/cloudflare/references/flagship/configuration.md deleted file mode 100644 index 8da90d5..0000000 --- a/external/develop/devops/cloudflare/references/flagship/configuration.md +++ /dev/null @@ -1,202 +0,0 @@ -# Flagship Configuration - -## Wrangler Binding Setup - -Add a Flagship binding to your Wrangler config to access flags via `env.FLAGS`. - -### Single App - -```jsonc -// wrangler.jsonc -{ - "flagship": { - "binding": "FLAGS", - "app_id": "" - } -} -``` - -```toml -# wrangler.toml -[flagship] -binding = "FLAGS" -app_id = "" -``` - -### Multiple Apps - -```jsonc -// wrangler.jsonc -{ - "flagship": [ - { - "binding": "FLAGS", - "app_id": "" - }, - { - "binding": "EXPERIMENT_FLAGS", - "app_id": "" - } - ] -} -``` - -```toml -# wrangler.toml -[[flagship]] -binding = "FLAGS" -app_id = "" - -[[flagship]] -binding = "EXPERIMENT_FLAGS" -app_id = "" -``` - -### Generate Types - -After adding the binding, generate TypeScript types: - -```bash -npx wrangler types -``` - -This creates the `Env` interface with each binding typed as `Flagship`: - -```typescript -interface Env { - FLAGS: Flagship; - EXPERIMENT_FLAGS: Flagship; // if multiple -} -``` - -The `Flagship` type comes from `@cloudflare/workers-types`. - ---- - -## OpenFeature SDK Installation - -### Server-Side (Workers, Node.js) - -```bash -npm i @cloudflare/flagship @openfeature/server-sdk -``` - -### Browser - -```bash -npm i @cloudflare/flagship @openfeature/web-sdk -``` - ---- - -## SDK Provider Setup - -### Server Provider — With Binding (Workers) - -Recommended approach inside Workers. No HTTP overhead, auth handled automatically. - -```typescript -import { OpenFeature } from "@openfeature/server-sdk"; -import { FlagshipServerProvider } from "@cloudflare/flagship"; - -export default { - async fetch(request: Request, env: Env): Promise { - await OpenFeature.setProviderAndWait( - new FlagshipServerProvider({ binding: env.FLAGS }), - ); - const client = OpenFeature.getClient(); - // ... evaluate flags - }, -}; -``` - -### Server Provider — With App ID (Node.js) - -For non-Worker runtimes. Requires an API token with Flagship read permissions. - -```typescript -import { OpenFeature } from "@openfeature/server-sdk"; -import { FlagshipServerProvider } from "@cloudflare/flagship"; - -await OpenFeature.setProviderAndWait( - new FlagshipServerProvider({ - appId: "", - accountId: "", - authToken: "", - }), -); -const client = OpenFeature.getClient(); -``` - -### Client Provider (Browser) - -Pre-fetches flags on init, then evaluates synchronously. Only `prefetchFlags` are available. - -```typescript -import { OpenFeature } from "@openfeature/web-sdk"; -import { FlagshipClientProvider } from "@cloudflare/flagship"; - -await OpenFeature.setProviderAndWait( - new FlagshipClientProvider({ - appId: "", - accountId: "", - authToken: "", - prefetchFlags: ["promo-banner", "dark-mode", "max-uploads"], - }), -); -await OpenFeature.setContext({ targetingKey: "user-42", plan: "enterprise" }); -const client = OpenFeature.getClient(); -``` - -### Provider Options Reference - -**FlagshipServerProvider:** - -| Option | Type | Required | Description | -|--------|------|----------|-------------| -| `binding` | `Flagship` | No | Binding from `env.FLAGS`. Use inside Workers. | -| `appId` | string | No | App ID from dashboard. Required without binding. | -| `accountId` | string | No | Cloudflare account ID. Required without binding. | -| `authToken` | string | No | API token with Flagship read permissions. Required without binding. | - -Provide either `binding` or all three of `appId` + `accountId` + `authToken`. - -**FlagshipClientProvider:** - -| Option | Type | Required | Description | -|--------|------|----------|-------------| -| `appId` | string | Yes | App ID from dashboard | -| `accountId` | string | Yes | Cloudflare account ID | -| `authToken` | string | Yes | API token with Flagship read permissions | -| `prefetchFlags` | string[] | Yes | Flag keys to prefetch. Unlisted flags return `FLAG_NOT_FOUND`. | - ---- - -## REST API Authentication - -For managing flags via the REST API (create, update, delete), set these environment variables: - -| Variable | Description | -|----------|-------------| -| `CLOUDFLARE_ACCOUNT_ID` | Your Cloudflare account ID | -| `CLOUDFLARE_API_TOKEN` | API token with Flagship permissions | -| `FLAGSHIP_APP_ID` | Target app UUID (from dashboard under **Compute > Flagship**, or `GET /apps`) | - -Base URL: `https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship` - -```bash -curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps" | jq . -``` - -App IDs are shown in the Cloudflare dashboard under **Compute > Flagship**. - ---- - -## Local Development - -Flagship bindings work in local dev with `wrangler dev`. Flag evaluation uses the live Flagship configuration — there is no local flag store. Ensure the `app_id` in your Wrangler config points to a valid app. - -```bash -npx wrangler dev -``` diff --git a/external/develop/devops/cloudflare/references/flagship/gotchas.md b/external/develop/devops/cloudflare/references/flagship/gotchas.md deleted file mode 100644 index 52429a6..0000000 --- a/external/develop/devops/cloudflare/references/flagship/gotchas.md +++ /dev/null @@ -1,178 +0,0 @@ -# Flagship Gotchas & Troubleshooting - -## Common Errors - -### Flag Always Returns Default Value - -**Cause:** Flag is disabled (`enabled: false`), or no targeting rules match, or evaluation context is missing expected attributes. - -**Solution:** Check these in order: - -1. Is the flag enabled? (`"enabled": true`) -2. Do your targeting rules match the context you're passing? -3. Are you passing the right attributes in the evaluation context? - -```typescript -// ❌ BAD — no context, rules can't match -const val = await env.FLAGS.getBooleanValue("my-flag", false); - -// ✅ GOOD — pass context attributes that rules reference -const val = await env.FLAGS.getBooleanValue("my-flag", false, { - userId: "user-42", - plan: "enterprise", -}); -``` - -### TYPE_MISMATCH Error in Details - -**Cause:** Calling a typed method on a flag with a different type (e.g., `getBooleanValue` on a string flag). - -**Solution:** Use the method matching the flag's variation type. - -```typescript -// ❌ BAD — flag "checkout-flow" has string variations -const val = await env.FLAGS.getBooleanValue("checkout-flow", false); - -// ✅ GOOD -const val = await env.FLAGS.getStringValue("checkout-flow", "original"); -``` - -### 409 Conflict on Flag Creation - -**Cause:** A flag with that key already exists in the app. - -**Solution:** Use a different key, or GET + PUT to update the existing flag. - -### Inconsistent Rollout Results - -**Cause:** `targetingKey` (or the configured bucketing attribute) is missing from the evaluation context, causing random bucketing on each request. - -**Solution:** Always pass a stable identifier: - -```typescript -// ❌ BAD — no targetingKey, rollout is random per request -const val = await env.FLAGS.getBooleanValue("gradual-rollout", false); - -// ✅ GOOD — stable userId for consistent bucketing -const val = await env.FLAGS.getBooleanValue("gradual-rollout", false, { - userId: sessionUserId, -}); -``` - -### Update Overwrites Entire Flag - -**Cause:** PUT replaces the full `FlagDefinition`. Sending only changed fields deletes the rest. - -**Solution:** Always read-modify-write: - -```bash -# ❌ BAD — overwrites the entire flag, losing rules/variations -curl -X PUT -d '{"enabled": true}' ... - -# ✅ GOOD — GET first, modify, PUT back -FLAG=$(curl -s -H "Authorization: Bearer $TOKEN" "$URL/flags/my-flag" | jq '.result') -UPDATED=$(echo "$FLAG" | jq '.enabled = true') -echo "$UPDATED" | curl -s -X PUT -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d @- "$URL/flags/my-flag" -``` - -### Reading REST Envelope Fields - -**Cause:** Management endpoints use Cloudflare v4 envelopes, not raw payloads. - -**Solution:** Read `.result` for successful payloads, `.result_info.cursor` for pagination, and `.errors[].message` for errors. - -```bash -jq '.result' -jq '.result_info.cursor' -jq '.errors[].message' -``` - -### Mixing CamelCase and Snake Case in REST Responses - -**Cause:** Management API responses are public API JSON and use snake_case. Evaluation responses use OpenFeature-style camelCase. - -**Solution:** For management endpoints use `default_variation`, `serve_variation`, `updated_at`, `updated_by`, and changelog `flag_key`. For `/evaluate`, use `flagKey`, `variant`, and `reason`. - -### FLAG_NOT_FOUND in Client Provider - -**Cause:** Flag key not included in `prefetchFlags` array. - -**Solution:** Add the flag key to `prefetchFlags` when initializing `FlagshipClientProvider`. - -### Client Provider Token Exposure - -**Cause:** The `authToken` passed to `FlagshipClientProvider` is visible in the browser. It can evaluate flags across all apps in the account. - -**Solution:** Use a token with minimal permissions (Flagship Evaluate only). Never use a token with write/management permissions in the browser. - ---- - -## Limits - -| Limit | Value | Notes | -|-------|-------|-------| -| Flag key length | 1-64 chars | Alphanumeric, hyphens, underscores only | -| Flag key pattern | `/^[a-zA-Z0-9_-]+$/` | — | -| Variation value size | 10KB max | Per variation, serialized | -| Variation name length | 64 chars max | Alphanumeric, hyphens, underscores | -| Description length | 512 chars max | Nullable | -| App name length | 1-64 chars | Alphanumeric, hyphens, underscores | -| Logical nesting depth | 6 levels | AND/OR conditions | -| Mutation rate limit | 60 / 60s | Per account:app | -| Read rate limit | 600 / 60s | Per account:app | -| Rollout percentage | 0-100 | Integer | -| Rule priorities | Unique integers >= 1 | Lower = evaluated first | - ---- - -## Anti-Patterns - -### Evaluating Flags in a Tight Loop - -Flag evaluation via the binding is fast but not free. Avoid evaluating the same flag repeatedly in a loop — evaluate once and reuse the result. - -```typescript -// ❌ BAD -for (const item of items) { - const enabled = await env.FLAGS.getBooleanValue("my-flag", false, ctx); - // ... -} - -// ✅ GOOD -const enabled = await env.FLAGS.getBooleanValue("my-flag", false, ctx); -for (const item of items) { - // use `enabled` -} -``` - -### Using the SDK Inside Workers When Binding Is Available - -The binding avoids HTTP overhead entirely. Only use the SDK inside Workers when you specifically need OpenFeature vendor-neutrality. - -```typescript -// ❌ Unnecessary HTTP overhead inside a Worker -const provider = new FlagshipServerProvider({ - appId: "...", accountId: "...", authToken: "...", -}); - -// ✅ Use the binding directly, or pass it to the SDK -const provider = new FlagshipServerProvider({ binding: env.FLAGS }); -``` - -### Partial PUT Updates - -The flag update API (PUT) requires the complete `FlagDefinition`. Sending only changed fields silently drops everything else. Always GET first, then modify and PUT back the full object. - -### Stale Flag Cleanup - -Flags that are disabled and no longer referenced in code should be deleted. Stale flags clutter the dashboard and make it harder to understand which flags are active. Follow the safe deletion workflow in `patterns.md`. - ---- - -## Propagation Behavior - -Flag changes propagate globally within seconds. During the brief propagation window, some regions may serve the previous value. After propagation completes, all evaluations return the updated value. - -- No Worker redeployment needed for flag changes. -- If the dashboard is temporarily unavailable, evaluation continues using the last propagated configuration. -- Flag changes made via the REST API and dashboard are equivalent — both trigger propagation. diff --git a/external/develop/devops/cloudflare/references/flagship/patterns.md b/external/develop/devops/cloudflare/references/flagship/patterns.md deleted file mode 100644 index 06fdbbd..0000000 --- a/external/develop/devops/cloudflare/references/flagship/patterns.md +++ /dev/null @@ -1,469 +0,0 @@ -# Flagship Patterns & Best Practices - -## Evaluating Flags in Workers (Binding) - -### Simple Boolean Toggle - -```typescript -export default { - async fetch(request: Request, env: Env): Promise { - const showNewUI = await env.FLAGS.getBooleanValue("new-ui", false, { - userId: "user-42", - }); - - if (showNewUI) { - return new Response("New UI"); - } - return new Response("Classic UI"); - }, -}; -``` - -### Multi-Variant String Flag - -```typescript -const checkoutFlow = await env.FLAGS.getStringValue( - "checkout-flow", - "original", - { userId, country: "US" }, -); - -switch (checkoutFlow) { - case "streamlined": - return handleStreamlined(request); - case "one-click": - return handleOneClick(request); - default: - return handleOriginal(request); -} -``` - -### JSON Config Flag - -```typescript -interface RateLimitConfig { - rpm: number; - burst: number; -} - -const limits = await env.FLAGS.getObjectValue( - "rate-limits", - { rpm: 100, burst: 20 }, - { plan: userPlan }, -); -``` - -### Using Details for Observability - -```typescript -const details = await env.FLAGS.getBooleanDetails("new-checkout", false, { - userId: "user-42", -}); - -console.log(details.value); // true -console.log(details.variant); // "on" -console.log(details.reason); // "TARGETING_MATCH" -console.log(details.errorCode); // undefined (no error) -``` - ---- - -## Evaluating Flags with OpenFeature (Workers) - -### Binding Passthrough (Recommended) - -```typescript -import { OpenFeature } from "@openfeature/server-sdk"; -import { FlagshipServerProvider } from "@cloudflare/flagship"; - -export default { - async fetch(request: Request, env: Env): Promise { - await OpenFeature.setProviderAndWait( - new FlagshipServerProvider({ binding: env.FLAGS }), - ); - const client = OpenFeature.getClient(); - - const enabled = await client.getBooleanValue("new-checkout", false, { - targetingKey: "user-42", - plan: "enterprise", - country: "US", - }); - - return new Response(enabled ? "New checkout" : "Standard checkout"); - }, -}; -``` - -### Migration from Another Provider - -Only the provider initialization changes — evaluation call sites stay the same: - -```typescript -// ❌ Before (LaunchDarkly) -await OpenFeature.setProviderAndWait( - new LaunchDarklyProvider({ sdkKey: "..." }), -); - -// ✅ After (Flagship) -await OpenFeature.setProviderAndWait( - new FlagshipServerProvider({ binding: env.FLAGS }), -); - -// Evaluation code is unchanged -const enabled = await client.getBooleanValue("my-flag", false, { - targetingKey: "user-42", -}); -``` - ---- - -## Managing Flags via REST API - -All examples use `api.cloudflare.com`. Set `CLOUDFLARE_ACCOUNT_ID`, `FLAGSHIP_APP_ID`, and `CLOUDFLARE_API_TOKEN` first. - -### Create a Boolean Flag - -```bash -curl -s -X POST \ - -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "key": "new-feature", - "default_variation": "off", - "variations": { "on": true, "off": false }, - "rules": [], - "description": "Enable the new feature", - "enabled": false - }' \ - "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps/$FLAGSHIP_APP_ID/flags" | jq . -``` - -### Create a Flag with Internal-Only Targeting - -```bash -curl -s -X POST \ - -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "key": "beta-feature", - "default_variation": "off", - "variations": { "on": true, "off": false }, - "rules": [ - { - "priority": 1, - "conditions": [ - { "attribute": "email", "operator": "ends_with", "value": "@cloudflare.com" } - ], - "serve_variation": "on" - } - ], - "description": "Beta feature for internal users", - "enabled": true - }' \ - "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps/$FLAGSHIP_APP_ID/flags" | jq . -``` - -### Create a JSON Config Flag - -```bash -curl -s -X POST \ - -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "key": "rate-limits", - "default_variation": "standard", - "variations": { - "standard": { "rpm": 100, "burst": 20 }, - "premium": { "rpm": 1000, "burst": 200 } - }, - "rules": [ - { - "priority": 1, - "conditions": [ - { "attribute": "plan", "operator": "in", "value": ["enterprise", "business"] } - ], - "serve_variation": "premium" - } - ], - "description": "Rate limit configuration by plan", - "enabled": true - }' \ - "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps/$FLAGSHIP_APP_ID/flags" | jq . -``` - -### Read a Flag - -```bash -curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps/$FLAGSHIP_APP_ID/flags/new-feature" | jq . -``` - -### List All Flags (with pagination) - -```bash -curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps/$FLAGSHIP_APP_ID/flags?limit=50" | jq . -``` - -If `result_info.cursor` is non-null, fetch the next page: - -```bash -curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps/$FLAGSHIP_APP_ID/flags?limit=50&cursor=" | jq . -``` - -### Update a Flag (Full Replace) - -Updates use PUT with the full `FlagDefinition`. Always GET first, modify, then PUT back. - -```bash -# 1. Read current flag -FLAG=$(curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps/$FLAGSHIP_APP_ID/flags/new-feature" | jq '.result') - -# 2. Modify (e.g., enable the flag) -UPDATED=$(echo "$FLAG" | jq '.enabled = true') - -# 3. PUT back -echo "$UPDATED" | curl -s -X PUT \ - -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - -H "Content-Type: application/json" \ - -d @- \ - "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps/$FLAGSHIP_APP_ID/flags/new-feature" | jq . -``` - -### Toggle a Flag On - -Read-modify-write to set `enabled: true`: - -```bash -BASE="https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps/$FLAGSHIP_APP_ID/flags" - -FLAG=$(curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" "$BASE/new-feature" | jq '.result') -UPDATED=$(echo "$FLAG" | jq '.enabled = true') -echo "$UPDATED" | curl -s -X PUT \ - -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - -H "Content-Type: application/json" \ - -d @- "$BASE/new-feature" | jq . -``` - -### Toggle a Flag Off (Disable) - -Same pattern, set `enabled: false`. The flag immediately returns its default variation for all evaluations. - -```bash -BASE="https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps/$FLAGSHIP_APP_ID/flags" - -FLAG=$(curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" "$BASE/new-feature" | jq '.result') -UPDATED=$(echo "$FLAG" | jq '.enabled = false') -echo "$UPDATED" | curl -s -X PUT \ - -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - -H "Content-Type: application/json" \ - -d @- "$BASE/new-feature" | jq . -``` - -### Add a Targeting Rule to an Existing Flag - -Append a rule to the existing rules array. Pick a priority that doesn't collide with existing rules. - -```bash -BASE="https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps/$FLAGSHIP_APP_ID/flags" - -FLAG=$(curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" "$BASE/new-feature" | jq '.result') -UPDATED=$(echo "$FLAG" | jq '.rules += [{ - "priority": 2, - "conditions": [{ "attribute": "plan", "operator": "equals", "value": "enterprise" }], - "serve_variation": "on" -}]') -echo "$UPDATED" | curl -s -X PUT \ - -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - -H "Content-Type: application/json" \ - -d @- "$BASE/new-feature" | jq . -``` - -### Change Rollout Percentage - -Update the rollout percentage on an existing rule (e.g., rule at index 0): - -```bash -BASE="https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps/$FLAGSHIP_APP_ID/flags" - -FLAG=$(curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" "$BASE/gradual-rollout" | jq '.result') -UPDATED=$(echo "$FLAG" | jq '.rules[0].rollout.percentage = 50') -echo "$UPDATED" | curl -s -X PUT \ - -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - -H "Content-Type: application/json" \ - -d @- "$BASE/gradual-rollout" | jq . -``` - -### Change Default Variation - -```bash -BASE="https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps/$FLAGSHIP_APP_ID/flags" - -FLAG=$(curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" "$BASE/new-feature" | jq '.result') -UPDATED=$(echo "$FLAG" | jq '.default_variation = "on"') -echo "$UPDATED" | curl -s -X PUT \ - -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - -H "Content-Type: application/json" \ - -d @- "$BASE/new-feature" | jq . -``` - -### Add a New Variation - -```bash -BASE="https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps/$FLAGSHIP_APP_ID/flags" - -FLAG=$(curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" "$BASE/checkout-flow" | jq '.result') -UPDATED=$(echo "$FLAG" | jq '.variations["treatment-c"] = "minimal"') -echo "$UPDATED" | curl -s -X PUT \ - -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - -H "Content-Type: application/json" \ - -d @- "$BASE/checkout-flow" | jq . -``` - -### Remove a Rule - -Remove a rule by filtering on priority: - -```bash -BASE="https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps/$FLAGSHIP_APP_ID/flags" - -FLAG=$(curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" "$BASE/new-feature" | jq '.result') -UPDATED=$(echo "$FLAG" | jq '.rules = [.rules[] | select(.priority != 2)]') -echo "$UPDATED" | curl -s -X PUT \ - -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - -H "Content-Type: application/json" \ - -d @- "$BASE/new-feature" | jq . -``` - -### Delete a Flag - -```bash -curl -s -X DELETE \ - -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/flagship/apps/$FLAGSHIP_APP_ID/flags/old-feature" | jq . -``` - ---- - -## Targeting Rule Patterns - -### Enterprise-Only Access - -```json -{ - "priority": 1, - "conditions": [ - { "attribute": "plan", "operator": "equals", "value": "enterprise" } - ], - "serve_variation": "on" -} -``` - -### Country-Based Targeting with Logical AND/OR - -Target enterprise users in the US or Canada: - -```json -{ - "priority": 1, - "conditions": [ - { - "logical_operator": "AND", - "clauses": [ - { "attribute": "plan", "operator": "equals", "value": "enterprise" }, - { - "logical_operator": "OR", - "clauses": [ - { "attribute": "country", "operator": "equals", "value": "US" }, - { "attribute": "country", "operator": "equals", "value": "CA" } - ] - } - ] - } - ], - "serve_variation": "on" -} -``` - -### Percentage Rollout - -Gradually roll out to 10% of users: - -```json -{ - "priority": 1, - "conditions": [ - { "attribute": "targetingKey", "operator": "not_equals", "value": "" } - ], - "serve_variation": "on", - "rollout": { - "percentage": 10, - "attribute": "targetingKey" - } -} -``` - -### A/B/n (Multi-Variant) Testing - -To split traffic across N variants, create one rule per variant with **cumulative** rollout percentages. Flagship evaluates rules in priority order. If a rule's conditions match but the user misses that rule's rollout percentage, evaluation continues to the next rule. Use the same stable rollout attribute on every rule so each user is compared against the same bucket as the thresholds increase. - -The example uses `conditions: []` because the rules are intended to match every context. For sticky user assignment, callers must still pass the configured bucketing attribute (`targetingKey` here); otherwise Flagship uses a random bucket per request. - -For example, to split traffic 30% / 40% / 30% across variants A, B, and C: - -| Variant | Share | Cumulative threshold | -|---------|-------|----------------------| -| A | 30% | 30 | -| B | 40% | 70 | -| C | 30% | 100 | - -```json -"rules": [ - { - "priority": 1, - "conditions": [], - "serve_variation": "variant-a", - "rollout": { "percentage": 30, "attribute": "targetingKey" } - }, - { - "priority": 2, - "conditions": [], - "serve_variation": "variant-b", - "rollout": { "percentage": 70, "attribute": "targetingKey" } - }, - { - "priority": 3, - "conditions": [], - "serve_variation": "variant-c", - "rollout": { "percentage": 100, "attribute": "targetingKey" } - } -] -``` - -Key points: -- Rules are evaluated lowest-priority-number first. A user who falls into rule 1's 0-30% bucket gets `variant-a` and is not evaluated further. -- Rule 2's 70% threshold covers the next 40% of users (31-70%). -- Rule 3's 100% threshold catches the remaining 30% (71-100%). -- Always set the last rule to `100` so every context with the bucketing attribute is assigned a variant. -- For sticky A/B/n assignment, pass a stable `targetingKey` or configured bucketing attribute. Without it, rollout assignment is random per request, which can be useful for request-level sampling but is usually wrong for user experiments. -- A percentage rollout match reports reason `SPLIT` in evaluation details. - -### Progressive Rollout Workflow - -1. Create flag with 5% rollout, enable it -2. Monitor metrics -3. Increase to 25% → 50% → 100% by updating the `rollout.percentage` -4. Once at 100%, remove the rule and set `default_variation` to the winning variation -5. Eventually remove the flag and the code branch - ---- - -## Safe Deletion Workflow - -1. **Disable** the flag first (`enabled: false`) — confirms nothing depends on it being active -2. **Monitor** for unexpected behavior -3. **Remove** flag evaluation code from your application -4. **Deploy** the code change -5. **Delete** the flag via API diff --git a/external/develop/devops/cloudflare/references/graphql-api/README.md b/external/develop/devops/cloudflare/references/graphql-api/README.md deleted file mode 100644 index 3f92dd5..0000000 --- a/external/develop/devops/cloudflare/references/graphql-api/README.md +++ /dev/null @@ -1,147 +0,0 @@ -# Cloudflare GraphQL Analytics API - -Query analytics data across all Cloudflare products via a single GraphQL endpoint. Covers HTTP requests, Workers metrics, DNS, Firewall events, Network Analytics, and 70+ other datasets. - -## Overview - -- **Single endpoint** for all analytics: `https://api.cloudflare.com/client/v4/graphql` -- **1,400+ schema types** spanning every Cloudflare product -- **Two scopes**: zone-level (per-domain) and account-level (cross-domain) -- **Adaptive sampling** on high-traffic datasets with confidence intervals -- **No mutations** - read-only analytics (the Mutation type is a stub) -- **Cost-based rate limiting** - default 300 queries per 5 minutes per user (max 320, varies by query cost) - -## Quick Decision Tree - -``` -Need analytics data from Cloudflare? -├─ HTTP traffic (requests, bandwidth, cache) → httpRequestsAdaptiveGroups (zone or account) -├─ Workers performance (CPU, wall time, errors) → workersInvocationsAdaptive (account) -├─ Firewall/WAF events → firewallEventsAdaptive / firewallEventsAdaptiveGroups (zone or account) -├─ DNS query analytics → dnsAnalyticsAdaptive / dnsAnalyticsAdaptiveGroups (zone or account) -├─ Network layer (DDoS, Magic Transit) → *NetworkAnalyticsAdaptiveGroups (account) -├─ Storage (R2, KV, D1, DO) → r2OperationsAdaptiveGroups / kvOperationsAdaptiveGroups / etc. (account) -├─ AI (Workers AI, AI Gateway) → aiInferenceAdaptive / aiGatewayRequestsAdaptiveGroups (account) -├─ Load Balancing → loadBalancingRequestsAdaptiveGroups (zone) -├─ Custom high-cardinality metrics → Workers Analytics Engine (see ../analytics-engine/) -└─ Need raw logs, not aggregates → Logpush (see Cloudflare docs) -``` - -## Core Concepts - -| Concept | Description | -|---------|-------------| -| **Endpoint** | `POST https://api.cloudflare.com/client/v4/graphql` | -| **Explorer** | [graphql.cloudflare.com](https://graphql.cloudflare.com/) - interactive query builder | -| **Viewer** | Root query object: `viewer { zones(...) { ... } }` or `viewer { accounts(...) { ... } }` | -| **Dataset (Node)** | A queryable table under a zone or account (e.g., `httpRequestsAdaptiveGroups`) | -| **Dimensions** | Fields to group by (time buckets, country, status code, script name, etc.) | -| **Metrics** | Aggregation fields: `count`, `sum { ... }`, `avg { ... }`, `quantiles { ... }`, `ratio { ... }` | -| **Filter** | Input object constraining results by time range, dimensions, etc. | -| **Limit** | Maximum rows returned per dataset node (required, max varies by dataset) | -| **OrderBy** | Enum-based sorting: `[field_ASC]` or `[field_DESC]` | -| **Adaptive Sampling** | Nodes with `Adaptive` in the name use ABR sampling; results are statistically representative | - -## Query Structure - -Every query follows this pattern: - -```graphql -{ - viewer { - # Zone-scoped - zones(filter: { zoneTag: "ZONE_ID" }) { - datasetName( - filter: { datetime_gt: "...", datetime_lt: "..." } - limit: 1000 - orderBy: [datetimeFiveMinutes_DESC] - ) { - count - dimensions { ... } - sum { ... } - } - } - # Account-scoped - accounts(filter: { accountTag: "ACCOUNT_ID" }) { - datasetName(filter: { ... }, limit: 100) { - count - dimensions { ... } - sum { ... } - } - } - } -} -``` - -## Dataset Naming Convention - -Dataset names follow a consistent pattern visible in the schema: - -| Pattern | Meaning | Example | -|---------|---------|---------| -| `*Adaptive` | Raw rows with adaptive sampling; some (e.g., `workersInvocationsAdaptive`) also support aggregation fields (`sum`, `quantiles`, `avg`) | `httpRequestsAdaptive`, `workersInvocationsAdaptive` | -| `*AdaptiveGroups` | Aggregated data with adaptive sampling | `httpRequestsAdaptiveGroups` | -| `*1hGroups` | Hourly rollups (pre-aggregated) | `httpRequests1hGroups` | -| `*1dGroups` | Daily rollups (pre-aggregated) | `httpRequests1dGroups` | -| `*1mGroups` | Minutely rollups | `httpRequests1mGroups` | -| `Zone*` prefix | Zone-scoped dataset | `ZoneHttpRequestsAdaptiveGroups` | -| `Account*` prefix | Account-scoped dataset | `AccountWorkersInvocationsAdaptive` | - -**Prefer `*AdaptiveGroups` nodes** for most use cases - they support flexible time grouping via dimension fields (`datetimeFiveMinutes`, `datetimeHour`, etc.) and are the most commonly used. - -## Key Datasets by Product - -### Zone-Scoped (per-domain) - -| Dataset | Description | -|---------|-------------| -| `httpRequestsAdaptiveGroups` | HTTP traffic: requests, bytes, cache status, bot scores, WAF scores | -| `httpRequests1hGroups` / `1dGroups` / `1mGroups` | Pre-aggregated HTTP rollups (hourly/daily/minutely) | -| `firewallEventsAdaptiveGroups` | WAF, rate limiting, bot management, firewall rule events | -| `dnsAnalyticsAdaptiveGroups` | DNS query volumes, response codes, query types | -| `loadBalancingRequestsAdaptiveGroups` | Load Balancer origin request metrics | -| `pageShieldReportsAdaptiveGroups` | Page Shield CSP reports | - -### Account-Scoped (cross-domain) - -| Dataset | Description | -|---------|-------------| -| `workersInvocationsAdaptive` | Workers: requests, errors, CPU time, wall time, subrequests | -| `durableObjectsInvocationsAdaptiveGroups` | DO invocations | -| `durableObjectsStorageGroups` / `durableObjectsPeriodicGroups` | DO storage and periodic metrics | -| `d1AnalyticsAdaptiveGroups` / `d1QueriesAdaptiveGroups` | D1 database analytics | -| `r2OperationsAdaptiveGroups` / `r2StorageAdaptiveGroups` | R2 operations and storage | -| `kvOperationsAdaptiveGroups` / `kvStorageAdaptiveGroups` | KV operations and storage | -| `aiInferenceAdaptiveGroups` | Workers AI inference metrics | -| `aiGatewayRequestsAdaptiveGroups` | AI Gateway request analytics | -| `pagesFunctionsInvocationsAdaptiveGroups` | Pages Functions metrics | -| `magicTransitNetworkAnalyticsAdaptiveGroups` | Magic Transit packet/byte analytics | -| `spectrumNetworkAnalyticsAdaptiveGroups` | Spectrum TCP/UDP analytics | -| `gatewayL7RequestsAdaptiveGroups` | Zero Trust Gateway HTTP metrics | -| `gatewayResolverQueriesAdaptiveGroups` | Zero Trust Gateway DNS metrics | - -## Reading Order - -| Task | Start Here | Then Read | -|------|------------|-----------| -| **First query** | [configuration.md](configuration.md) (auth) -> this README (structure) | [api.md](api.md) | -| **Build a dashboard** | [patterns.md](patterns.md) (time-series, top-N) | [api.md](api.md) (aggregation fields) | -| **Debug query issues** | [gotchas.md](gotchas.md) | [api.md](api.md) (filtering) | -| **Understand sampling** | [gotchas.md](gotchas.md) (sampling section) | [api.md](api.md) (confidence intervals) | -| **Product-specific metrics** | [patterns.md](patterns.md) (per-product examples) | [api.md](api.md) (dataset reference) | - -## In This Reference - -- **[api.md](api.md)** - Query structure, aggregation fields (sum/avg/quantiles/count), filtering operators, dimensions, dataset details -- **[configuration.md](configuration.md)** - Authentication, API tokens, client setup (curl, JS, Python), introspection -- **[patterns.md](patterns.md)** - Common queries: time-series, top-N, Workers metrics, HTTP analytics, firewall events, multi-zone -- **[gotchas.md](gotchas.md)** - Rate limits, sampling caveats, query cost, common errors, plan-based limits - -## See Also - -- [GraphQL Analytics API Docs](https://developers.cloudflare.com/analytics/graphql-api/) -- [GraphQL API Explorer](https://graphql.cloudflare.com/) -- [Observability Reference](../observability/) - Workers Logs, Tail Workers, console logging -- [Analytics Engine Reference](../analytics-engine/) - Custom high-cardinality analytics via Workers -- [Web Analytics Reference](../web-analytics/) - Client-side (RUM) analytics -- [API Reference](../api/) - REST API, SDKs, authentication basics diff --git a/external/develop/devops/cloudflare/references/graphql-api/api.md b/external/develop/devops/cloudflare/references/graphql-api/api.md deleted file mode 100644 index 15d1a33..0000000 --- a/external/develop/devops/cloudflare/references/graphql-api/api.md +++ /dev/null @@ -1,175 +0,0 @@ -# GraphQL Analytics API Reference - -## Query Root - -The schema has a single entry point: `Query.viewer`. Mutations are not supported. - -```graphql -{ - cost # uint64 -- query cost (returned in response) - viewer { - budget # uint64 -- remaining budget - zones(filter: { zoneTag: "..." }) { ... } - accounts(filter: { accountTag: "..." }) { ... } - } -} -``` - -## Aggregation Fields - -Aggregated dataset nodes (`*Groups`) return these field categories. Not every node has all — use introspection to check. - -### count - -Total events in the group. Available on `*Groups` nodes but **not** on raw `*Adaptive` nodes (e.g., `workersInvocationsAdaptive` — use `sum { requests }` instead). - -### sum - -Cumulative metrics. Fields vary by dataset: - -```graphql -# HTTP requests -sum { edgeResponseBytes edgeRequestBytes visits edgeTimeToFirstByteMs originResponseDurationMs } - -# Workers invocations -sum { requests errors subrequests cpuTimeUs wallTime duration responseBodySize clientDisconnects requestDuration } -``` - -### quantiles - -Percentile distributions (on datasets like `workersInvocationsAdaptive`). Available percentiles: P25, P50, P75, P90, P95, P99, P999 for `cpuTime`, `wallTime`, `requestDuration`, `duration`, `responseBodySize`. - -```graphql -quantiles { cpuTimeP50 cpuTimeP99 wallTimeP50 wallTimeP99 } -``` - -### ratio, avg, uniq, confidence - -```graphql -ratio { status4xx status5xx } # float64 (0 to 1) -- HTTP datasets only -avg { sampleInterval } # useful for understanding sampling resolution -uniq { uniques } # unique IP count -- rollup datasets (*1hGroups, *1dGroups) only -confidence(level: 0.95) { # Adaptive datasets only; works on count and sum fields - count { estimate lower upper sampleSize } -} -``` - -## Dimensions - -Dimensions are fields you can group by via the `dimensions` sub-selection. - -### Time Dimensions - -| Dimension | Granularity | -|-----------|------------| -| `date` | Day | -| `datetime` | Exact timestamp | -| `datetimeMinute` | 1 minute | -| `datetimeFiveMinutes` | 5 minutes | -| `datetimeFifteenMinutes` | 15 minutes | -| `datetimeHour` | 1 hour | - -Workers datasets also support `datetimeSixHours`. - -### HTTP Request Dimensions (httpRequestsAdaptiveGroups) - -83 dimensions available. Key ones: - -| Dimension | Description | -|-----------|-------------| -| `clientCountryName` | Country of origin | -| `clientRequestHTTPHost` | Requested hostname | -| `clientRequestHTTPMethodName` | HTTP method | -| `clientRequestPath` | URI path | -| `edgeResponseStatus` | Edge HTTP status code | -| `cacheStatus` | Cache status (hit, miss, dynamic, etc.) | -| `coloCode` | Cloudflare datacenter IATA code | -| `clientIP` / `clientAsn` | Client IP address / ASN | -| `botScore` / `botManagementDecision` | Bot management score (0-99) / verdict | -| `wafAttackScore` / `securityAction` | WAF score / firewall action taken | -| `ja3Hash` / `ja4` | TLS fingerprints | -| `sampleInterval` | ABR sample interval | - -### Workers Dimensions (workersInvocationsAdaptive) - -`scriptName`, `scriptTag`, `scriptVersion`, `environmentName`, `status`, `usageModel`, `coloCode`, `dispatchNamespaceName`, `isDispatcher` - -### Firewall Dimensions (firewallEventsAdaptive) - -`action`, `source`, `ruleId`, `clientCountryName`, `clientIP`, `clientAsn`, `userAgent` - -## Filtering - -### Scope Filters - -```graphql -zones(filter: { zoneTag: "ZONE_ID" }) # up to 10 zones -zones(filter: { zoneTag_in: ["Z1", "Z2"] }) -accounts(filter: { accountTag: "ACCOUNT_ID" }) # exactly 1 account -``` - -### Dataset Filters - -**Always include a time range filter.** Multiple filters at the same level are implicitly AND-ed. - -```graphql -httpRequestsAdaptiveGroups( - filter: { datetime_gt: "2025-01-01T00:00:00Z", datetime_lt: "2025-01-02T00:00:00Z", clientCountryName: "US" } - limit: 1000 -) -``` - -### Filter Operators - -| Operator | Meaning | Example | -|----------|---------|---------| -| (none) | equals | `clientCountryName: "US"` | -| `_gt` / `_lt` | greater / less than | `datetime_gt: "..."` | -| `_geq` / `_leq` | greater/less or equal | `datetime_geq: "..."` | -| `_neq` | not equal | `cacheStatus_neq: "hit"` | -| `_in` / `_notin` | in / not in list | `clientCountryName_in: ["US", "GB"]` | -| `_like` / `_notlike` | SQL LIKE with `%` | `clientRequestPath_like: "/api/%"` | -| `_has` / `_hasall` / `_hasany` | array contains | `botDetectionIds_has: "abc"` | - -> `_notin` and `_notlike` are in the schema but not in official docs. Confirmed via introspection. - -### Boolean Operators (AND / OR) - -```graphql -# Explicit AND -filter: { AND: [{ datetime_gt: "..." }, { datetime_lt: "..." }, { clientCountryName: "US" }] } - -# Explicit OR -filter: { datetime_gt: "...", OR: [{ edgeResponseStatus: 403 }, { edgeResponseStatus: 429 }] } -``` - -## Pagination & Sorting - -No cursor-based pagination. Use `limit`, `orderBy`, and filter-based offsets: - -```graphql -# First page -httpRequestsAdaptiveGroups(filter: { datetime_gt: "..." }, limit: 100, orderBy: [datetime_ASC]) - -# Next page: filter by last seen value from previous page -httpRequestsAdaptiveGroups(filter: { datetime_gt: "2025-01-01T01:35:00Z" }, limit: 100, orderBy: [datetime_ASC]) -``` - -Sort with `orderBy: [field_ASC]` or `[field_DESC]`. Multiple sort fields supported. - -## Settings Node - -Query per-node limits and availability: - -```graphql -viewer { zones(filter: { zoneTag: "..." }) { settings { - httpRequestsAdaptiveGroups { enabled maxDuration maxNumberOfFields maxPageSize notOlderThan } -} } } -``` - -## See Also - -- [README.md](README.md) - Overview, decision tree, dataset index -- [configuration.md](configuration.md) - Authentication, client setup, introspection queries -- [patterns.md](patterns.md) - Common query patterns (time-series, top-N, per-product) -- [gotchas.md](gotchas.md) - Rate limits, sampling, troubleshooting diff --git a/external/develop/devops/cloudflare/references/graphql-api/configuration.md b/external/develop/devops/cloudflare/references/graphql-api/configuration.md deleted file mode 100644 index d85d1a6..0000000 --- a/external/develop/devops/cloudflare/references/graphql-api/configuration.md +++ /dev/null @@ -1,118 +0,0 @@ -# GraphQL Analytics API Configuration - -## Authentication - -### API Token (Recommended) - -| Permission | Scope | Use Case | -|------------|-------|----------| -| **Account Analytics: Read** | Account-wide | Workers, R2, KV, D1, DO, AI, Network Analytics | -| **Zone Analytics: Read** | Per-zone | HTTP requests, Firewall, DNS, Load Balancing | -| **All zones - Analytics: Read** | All zones | Multi-zone HTTP/Firewall/DNS queries | - -Create tokens at: [dash.cloudflare.com > Account API Tokens](https://dash.cloudflare.com/?to=/:account/api-tokens) - -```bash -# Verify token -curl -s https://api.cloudflare.com/client/v4/graphql \ - -H "Authorization: Bearer $CF_API_TOKEN" \ - -H "Content-Type: application/json" \ - --data '{"query":"{ viewer { zones(filter: {zoneTag: \"ZONE_ID\"}) { httpRequestsAdaptiveGroups(limit: 1, filter: {datetime_gt: \"2025-01-01T00:00:00Z\"}) { count } } } }"}' -``` - -### API Key + Email (Legacy) - -Not recommended. Use `X-Auth-Email` + `X-Auth-Key` headers instead of `Authorization: Bearer`. - -## Client Setup - -### curl - -```bash -curl -s https://api.cloudflare.com/client/v4/graphql \ - -H "Authorization: Bearer $CF_API_TOKEN" \ - -H "Content-Type: application/json" \ - --data '{ - "query": "query($zoneTag: string!, $start: Time!, $end: Time!) { viewer { zones(filter: {zoneTag: $zoneTag}) { httpRequestsAdaptiveGroups(filter: {datetime_gt: $start, datetime_lt: $end}, limit: 10, orderBy: [datetimeFiveMinutes_DESC]) { count dimensions { datetimeFiveMinutes } } } } }", - "variables": { "zoneTag": "ZONE_ID", "start": "2025-01-01T00:00:00Z", "end": "2025-01-02T00:00:00Z" } - }' | jq . -``` - -### TypeScript / JavaScript - -```typescript -const GRAPHQL_ENDPOINT = "https://api.cloudflare.com/client/v4/graphql"; - -async function queryGraphQL(query: string, variables: Record = {}): Promise { - const response = await fetch(GRAPHQL_ENDPOINT, { - method: "POST", - headers: { Authorization: `Bearer ${process.env.CF_API_TOKEN}`, "Content-Type": "application/json" }, - body: JSON.stringify({ query, variables }), - }); - if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); - const json = await response.json() as { data: T | null; errors?: { message: string }[] }; - if (json.errors?.length) throw new Error(json.errors.map((e) => e.message).join("; ")); - return json.data!; -} -``` - -### Python - -```python -import requests, os - -def query_graphql(query: str, variables: dict = None) -> dict: - r = requests.post("https://api.cloudflare.com/client/v4/graphql", - headers={"Authorization": f"Bearer {os.environ['CF_API_TOKEN']}", "Content-Type": "application/json"}, - json={"query": query, "variables": variables or {}}) - r.raise_for_status() - result = r.json() - if result.get("errors"): - raise Exception("; ".join(e["message"] for e in result["errors"])) - return result["data"] -``` - -### From a Cloudflare Worker - -Store the API token as a secret (`CF_API_TOKEN`). Use standard `fetch` to POST to `https://api.cloudflare.com/client/v4/graphql` with the same JSON body format as above. Always check `response.errors` — GraphQL returns 200 even on query failures. - -## GraphQL API Explorer - -Interactive explorer at [graphql.cloudflare.com](https://graphql.cloudflare.com/) — provides schema docs, autocomplete, variable panel, and shareable queries. Authenticates via your Cloudflare dashboard session. - -## Schema Introspection - -```graphql -# List zone-scoped datasets -{ __type(name: "zone") { fields { name description } } } - -# List account-scoped datasets -{ __type(name: "account") { fields { name description } } } - -# Discover dimensions for a dataset -{ __type(name: "ZoneHttpRequestsAdaptiveGroupsDimensions") { - fields { name type { name kind } } -} } - -# Discover filter operators for a dataset -{ __type(name: "ZoneHttpRequestsAdaptiveGroupsFilter_InputObject") { - inputFields { name type { name kind } } -} } -``` - -## Finding Your Zone and Account IDs - -- **Zone ID**: Dashboard > select zone > Overview (right sidebar), or via API -- **Account ID**: Dashboard > Account Home URL, or via API - -```bash -curl -s https://api.cloudflare.com/client/v4/zones -H "Authorization: Bearer $CF_API_TOKEN" | jq '.result[] | {name, id}' -curl -s https://api.cloudflare.com/client/v4/accounts -H "Authorization: Bearer $CF_API_TOKEN" | jq '.result[] | {name, id}' -``` - -## See Also - -- [README.md](README.md) - Overview, decision tree, dataset index -- [api.md](api.md) - Query structure, aggregation fields, filtering operators -- [patterns.md](patterns.md) - Common query patterns (time-series, top-N, per-product) -- [gotchas.md](gotchas.md) - Rate limits, sampling, troubleshooting diff --git a/external/develop/devops/cloudflare/references/graphql-api/gotchas.md b/external/develop/devops/cloudflare/references/graphql-api/gotchas.md deleted file mode 100644 index 18df7c6..0000000 --- a/external/develop/devops/cloudflare/references/graphql-api/gotchas.md +++ /dev/null @@ -1,110 +0,0 @@ -# GraphQL Analytics API Gotchas & Troubleshooting - -## Rate Limits - -| Limit | Value | -|-------|-------| -| GraphQL queries per user | **Default 300 per 5 minutes** (max 320, at least 1/sec) | -| General API rate limit | 1200 requests per 5 minutes (shared across all API calls) | -| Zone scope per query | Up to **10 zones** | -| Account scope per query | Exactly **1 account** | - -The GraphQL rate limit is separate from the general API limit. Exceeding either results in `HTTP 429` and blocks all API calls for 5 minutes. Enterprise customers can contact support to raise limits. - -### "429 Too Many Requests" - -**Cause:** Exceeded rate limit. - -**Solution:** Batch multiple datasets into single queries, cache results, increase intervals between queries. Use `{ viewer { budget } }` to monitor remaining budget. - -## Sampling & Data Accuracy - -### Adaptive Bit Rate (ABR) Sampling - -Datasets with `Adaptive` in the name use adaptive sampling: -- Results are **statistically representative**, not exact -- Same query may return **slightly different numbers** each run -- Higher traffic = higher sampling rate = more accurate -- `sampleInterval` dimension shows the ratio (1 = no sampling, 10 = ~1-in-10 sampled) - -For high-confidence numbers, use `confidence(level: 0.95)` to get estimate bounds. For exact counts, use rollup nodes (`httpRequests1hGroups`, `httpRequests1dGroups`) which are pre-aggregated without sampling. - -### Rollup vs. Adaptive - -| Feature | Rollup (`*1hGroups`, `*1dGroups`) | Adaptive (`*AdaptiveGroups`) | -|---------|-----------------------------------|-----------------------------| -| Sampling | No (pre-aggregated) | Yes (ABR) | -| Flexibility | Fixed time buckets | Any granularity | -| Dimensions | Fewer | Many more | -| Accuracy | Exact | Statistical estimate | - -## Common Errors - -### "Access denied" / "authentication error" - -**Cause:** Token lacks required permission or wrong scope. - -**Solution:** Account-scoped queries need **Account Analytics: Read**. Zone-scoped queries need **Zone Analytics: Read**. Verify: `curl -s https://api.cloudflare.com/client/v4/user/tokens/verify -H "Authorization: Bearer $TOKEN"` - -### "field not found" / "Cannot query field" - -**Cause:** Wrong dataset name, nonexistent field, or wrong scope (zone vs. account). - -**Solution:** Names are case-sensitive camelCase (`httpRequestsAdaptiveGroups`). Zone datasets go under `zones(...)`, account datasets under `accounts(...)`. Use introspection to verify. - -### "filter is required" / empty results - -**Cause:** Missing required time range filter or incorrect zone/account tag. - -**Solution:** Always include `datetime_gt` / `datetime_lt` (or `_geq` / `_leq`). - -### "limit is required" / "limit exceeds maximum" - -**Cause:** Missing `limit` or exceeding node's max page size. - -**Solution:** Always specify `limit`. Max varies by dataset (typically 10,000 for groups, 100 for raw events). Check via settings query. - -### "query is too complex" / "query exceeds budget" - -**Cause:** Too many fields, datasets, or too broad a time range. - -**Solution:** Reduce time range, request fewer dimensions/metrics, break into smaller queries. Monitor `cost` and `budget` in responses. - -### 200 Response with Errors - -GraphQL returns HTTP 200 even on failures. **Always check `response.errors`:** - -```json -{ "data": null, "errors": [{ "message": "filter is required for httpRequestsAdaptiveGroups" }] } -``` - -## Plan-Based Availability - -Not all datasets are available on all plans. Higher plans get more datasets, longer retention (`notOlderThan`), wider time ranges (`maxDuration`), more fields, and larger page sizes. - -### "node is not available" / "node is disabled" - -**Cause:** Dataset not on your plan, or product not enabled. - -**Solution:** Check `settings { { enabled } }`. Some datasets require specific subscriptions (e.g., Network Analytics requires Magic Transit/Spectrum). - -## DateTime & Timezone Handling - -- All times are **UTC only** (ISO 8601: `"2025-01-15T10:30:00Z"`) -- `Date` type: `"2025-01-15"` (used in `date_geq`/`date_leq` for storage datasets) -- `Time` type: `"2025-01-15T10:30:00Z"` (used in `datetime_gt`/`datetime_lt`) -- Filters are start-inclusive: events that start within the window are included - -## Performance Tips - -- **Narrow time ranges** are faster and cheaper -- **Select only needed dimensions** — each additional dimension increases cost -- **Use rollup nodes** (`*1dGroups`) for simple daily totals without dimension breakdowns -- **Batch datasets** into one query instead of separate HTTP requests - -## See Also - -- [README.md](README.md) - Overview, decision tree, dataset index -- [api.md](api.md) - Query structure, aggregation fields, filtering operators -- [configuration.md](configuration.md) - Authentication, client setup, introspection queries -- [patterns.md](patterns.md) - Common query patterns (time-series, top-N, per-product) diff --git a/external/develop/devops/cloudflare/references/graphql-api/patterns.md b/external/develop/devops/cloudflare/references/graphql-api/patterns.md deleted file mode 100644 index f02c36b..0000000 --- a/external/develop/devops/cloudflare/references/graphql-api/patterns.md +++ /dev/null @@ -1,225 +0,0 @@ -# GraphQL Analytics API Patterns & Best Practices - -## Time-Series Queries - -Use time dimension granularity matching your range (see Best Practices below). - -```graphql -query TrafficTimeSeries($zoneTag: string!, $start: Time!, $end: Time!) { - viewer { - zones(filter: { zoneTag: $zoneTag }) { - httpRequestsAdaptiveGroups( - filter: { datetime_gt: $start, datetime_lt: $end } - limit: 1000 - orderBy: [datetimeFiveMinutes_ASC] # or datetimeHour_ASC for longer ranges - ) { - count - dimensions { datetimeFiveMinutes } - sum { edgeResponseBytes } - ratio { status4xx status5xx } - } - } - } -} -``` - -## Top-N Queries - -### Top Countries by Request Count - -```graphql -query TopCountries($zoneTag: string!, $start: Time!, $end: Time!) { - viewer { - zones(filter: { zoneTag: $zoneTag }) { - httpRequestsAdaptiveGroups( - filter: { datetime_gt: $start, datetime_lt: $end } - limit: 10 - orderBy: [count_DESC] - ) { - count - dimensions { clientCountryName } - } - } - } -} -``` - -Use `orderBy: [sum_edgeResponseBytes_DESC]` for top paths by bandwidth. Add `edgeResponseStatus_geq: 400` to the filter for top error status codes. - -## Workers Analytics - -```graphql -query WorkersOverview($accountTag: string!, $start: Time!, $end: Time!) { - viewer { - accounts(filter: { accountTag: $accountTag }) { - workersInvocationsAdaptive( - filter: { datetime_gt: $start, datetime_lt: $end } - limit: 100 - orderBy: [sum_requests_DESC] - ) { - sum { requests errors subrequests wallTime } - quantiles { cpuTimeP50 cpuTimeP99 wallTimeP50 wallTimeP99 } - dimensions { scriptName } - } - } - } -} -``` - -Filter by `scriptName` for a specific Worker. Add `datetimeFiveMinutes` dimension + `orderBy: [datetimeFiveMinutes_ASC]` for error rate over time. - -## Firewall / Security - -```graphql -query RecentFirewallEvents($zoneTag: string!, $start: Time!) { - viewer { - zones(filter: { zoneTag: $zoneTag }) { - firewallEventsAdaptive( - filter: { datetime_gt: $start } - limit: 50 - orderBy: [datetime_DESC] - ) { - action source clientIP clientCountryName userAgent - clientRequestHTTPHost clientRequestPath ruleId datetime - } - } - } -} -``` - -For aggregated firewall stats, use `firewallEventsAdaptiveGroups` with `action: "block"` filter and group by `ruleId`, `source`, `datetimeHour`. - -## DNS Analytics - -```graphql -query DNSQueryVolume($zoneTag: string!, $start: Time!, $end: Time!) { - viewer { - zones(filter: { zoneTag: $zoneTag }) { - dnsAnalyticsAdaptiveGroups( - filter: { datetime_gt: $start, datetime_lt: $end } - limit: 500 - orderBy: [datetimeFiveMinutes_ASC] - ) { - count - dimensions { datetimeFiveMinutes } - } - } - } -} -``` - -## Storage Analytics (Account-Scoped) - -R2, KV, and D1 use `date` (Date type) filters instead of `datetime` (Time type). - -```graphql -# R2 operations -r2OperationsAdaptiveGroups(filter: { date_geq: $start, date_leq: $end }, limit: 100, orderBy: [date_DESC]) { - dimensions { date bucketName actionType } - sum { requests } -} - -# KV operations -kvOperationsAdaptiveGroups(filter: { date_geq: $start, date_leq: $end }, limit: 100, orderBy: [date_DESC]) { - dimensions { date actionType } - sum { requests } -} - -# D1 analytics -d1AnalyticsAdaptiveGroups(filter: { date_geq: $start, date_leq: $end }, limit: 100, orderBy: [date_DESC]) { - dimensions { date databaseId } - sum { readQueries writeQueries rowsRead rowsWritten } -} -``` - -## Cache Analytics - -```graphql -query CacheStatusBreakdown($zoneTag: string!, $start: Time!, $end: Time!) { - viewer { - zones(filter: { zoneTag: $zoneTag }) { - httpRequestsAdaptiveGroups( - filter: { datetime_gt: $start, datetime_lt: $end } - limit: 20 - orderBy: [count_DESC] - ) { - count - dimensions { cacheStatus } - sum { edgeResponseBytes } - } - } - } -} -``` - -For cache hit ratio over time, use aliases to query the same dataset twice — once with `cacheStatus: "hit"` filter and once without — then compute the ratio client-side. - -## Multi-Dataset Queries - -A single request can query multiple datasets, avoiding extra HTTP round-trips: - -```graphql -query DashboardOverview($zoneTag: string!, $start: Time!, $end: Time!) { - viewer { - zones(filter: { zoneTag: $zoneTag }) { - httpTraffic: httpRequestsAdaptiveGroups( - filter: { datetime_gt: $start, datetime_lt: $end }, limit: 1 - ) { count sum { edgeResponseBytes } ratio { status4xx status5xx } } - firewallEvents: firewallEventsAdaptiveGroups( - filter: { datetime_gt: $start, datetime_lt: $end }, limit: 5, orderBy: [count_DESC] - ) { count dimensions { action source } } - dnsQueries: dnsAnalyticsAdaptiveGroups( - filter: { datetime_gt: $start, datetime_lt: $end }, limit: 1 - ) { count } - } - } -} -``` - -## AI & Gateway Analytics - -```graphql -# Workers AI inference -aiInferenceAdaptiveGroups( - filter: { datetime_gt: $start, datetime_lt: $end }, limit: 100, orderBy: [datetimeHour_DESC] -) { - count - sum { totalInputTokens totalOutputTokens totalRequestBytesIn } - dimensions { modelId datetimeHour } -} - -# AI Gateway requests -aiGatewayRequestsAdaptiveGroups( - filter: { datetime_gt: $start, datetime_lt: $end }, limit: 100, orderBy: [datetimeHour_DESC] -) { - count - dimensions { gateway provider model datetimeHour } - sum { cachedTokensIn cachedTokensOut uncachedTokensIn uncachedTokensOut } -} -``` - -Both are account-scoped — nest under `accounts(filter: { accountTag: $accountTag })`. - -## Best Practices - -**Always include time filters.** Queries without time filters scan all data and are slow/expensive. - -**Match time granularity to range:** - -| Time Range | Recommended Dimension | -|------------|----------------------| -| < 6 hours | `datetimeMinute` or `datetimeFiveMinutes` | -| 6-48 hours | `datetimeFiveMinutes` or `datetimeFifteenMinutes` | -| 2-14 days | `datetimeHour` | -| 14+ days | `date` | - -**Use aliases** for querying the same dataset with different filters in one request. - -**Request only needed fields.** Extra dimensions and metrics increase query cost. - -## See Also - -- [README.md](README.md) - Overview, decision tree, dataset index -- [api.md](api.md) - Query structure, aggregation fields, filtering operators -- [configuration.md](configuration.md) - Authentication, client setup, introspection queries -- [gotchas.md](gotchas.md) - Rate limits, sampling, troubleshooting diff --git a/external/develop/devops/cloudflare/references/hyperdrive/README.md b/external/develop/devops/cloudflare/references/hyperdrive/README.md index e40e9e7..8a07a1b 100644 --- a/external/develop/devops/cloudflare/references/hyperdrive/README.md +++ b/external/develop/devops/cloudflare/references/hyperdrive/README.md @@ -1,6 +1,6 @@ # Hyperdrive -Use Hyperdrive to connect Workers to an existing PostgreSQL or MySQL database with connection pooling and optional query caching. It does not replace the origin database or replicate its data. Start with [how Hyperdrive works](https://developers.cloudflare.com/hyperdrive/concepts/how-hyperdrive-works/) and [supported databases and features](https://developers.cloudflare.com/hyperdrive/reference/supported-databases-and-features/) to assess fit. +Use Hyperdrive to connect Workers to an existing PostgreSQL or MySQL database with connection pooling and optional query caching. It does not replace the origin database or replicate its data. Start with [how Hyperdrive works](https://developers.cloudflare.com/hyperdrive/concepts/how-hyperdrive-works/index.md) and [supported databases and features](https://developers.cloudflare.com/hyperdrive/reference/supported-databases-and-features/index.md) to assess fit. ## Retrieve current documentation @@ -18,10 +18,11 @@ Fetch the relevant official page before implementing. Driver versions, compatibi ## Decisions to preserve - Choose a driver for the database engine and existing application stack; verify supported versions and Worker requirements in its guide. -- Create database clients inside each handler invocation. Hyperdrive manages the underlying origin pool; consult [connection lifecycle](https://developers.cloudflare.com/hyperdrive/concepts/connection-lifecycle/) for cleanup behavior. -- Choose caching by read freshness. Disabling caching still allows connection pooling; a write does not invalidate cached reads. See [query caching](https://developers.cloudflare.com/hyperdrive/concepts/query-caching/). +- Create database clients inside each handler invocation. Hyperdrive manages the underlying origin pool; consult [connection lifecycle](https://developers.cloudflare.com/hyperdrive/concepts/connection-lifecycle/index.md) for cleanup behavior. +- Choose caching by read freshness. Disabling caching still allows connection pooling; a write does not invalidate cached reads. See [query caching](https://developers.cloudflare.com/hyperdrive/concepts/query-caching/index.md). +- For multiple sequential queries to one regional database, configure explicit Worker placement matching its provider and region before deployment. Hyperdrive does not automatically place the Worker near the origin. Follow [placement setup](./configuration.md#place-compute-near-the-database), preserving explicit user choices. ## See also - [D1](../d1/) for a managed SQLite alternative. -- [Workers](https://developers.cloudflare.com/workers/) for the runtime and bindings. +- [Workers](https://developers.cloudflare.com/workers/index.md) for the runtime and bindings. diff --git a/external/develop/devops/cloudflare/references/hyperdrive/api.md b/external/develop/devops/cloudflare/references/hyperdrive/api.md index 3f43eb5..6464e09 100644 --- a/external/develop/devops/cloudflare/references/hyperdrive/api.md +++ b/external/develop/devops/cloudflare/references/hyperdrive/api.md @@ -6,21 +6,21 @@ Start with [README.md](./README.md) and [configuration.md](./configuration.md). | Task | Official documentation | |------|------------------------| -| PostgreSQL with node-postgres (`pg`), including binding connection string and parameterized queries | [node-postgres](https://developers.cloudflare.com/hyperdrive/examples/connect-to-postgres/postgres-drivers-and-libraries/node-postgres/) | -| PostgreSQL with tagged-template queries and Postgres.js driver options | [Postgres.js](https://developers.cloudflare.com/hyperdrive/examples/connect-to-postgres/postgres-drivers-and-libraries/postgres-js/) | -| MySQL with binding connection properties and Worker-specific driver options | [mysql2](https://developers.cloudflare.com/hyperdrive/examples/connect-to-mysql/mysql-drivers-and-libraries/mysql2/) | -| Check database features, prepared statements, and library compatibility | [Supported databases and features](https://developers.cloudflare.com/hyperdrive/reference/supported-databases-and-features/) | -| Generate binding and runtime TypeScript types | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/) | +| PostgreSQL with node-postgres (`pg`), including binding connection string and parameterized queries | [node-postgres](https://developers.cloudflare.com/hyperdrive/examples/connect-to-postgres/postgres-drivers-and-libraries/node-postgres/index.md) | +| PostgreSQL with tagged-template queries and Postgres.js driver options | [Postgres.js](https://developers.cloudflare.com/hyperdrive/examples/connect-to-postgres/postgres-drivers-and-libraries/postgres-js/index.md) | +| MySQL with binding connection properties and Worker-specific driver options | [mysql2](https://developers.cloudflare.com/hyperdrive/examples/connect-to-mysql/mysql-drivers-and-libraries/mysql2/index.md) | +| Check database features, prepared statements, and library compatibility | [Supported databases and features](https://developers.cloudflare.com/hyperdrive/reference/supported-databases-and-features/index.md) | +| Generate binding and runtime TypeScript types | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/index.md) | -Keep an existing supported driver when it fits the application. Choose by database engine and library integration needs; do not infer cache behavior from a driver's prepared-statement setting. Fetch [query caching](https://developers.cloudflare.com/hyperdrive/concepts/query-caching/) for cache eligibility and freshness controls. +Keep an existing supported driver when it fits the application. Choose by database engine and library integration needs; do not infer cache behavior from a driver's prepared-statement setting. Fetch [query caching](https://developers.cloudflare.com/hyperdrive/concepts/query-caching/index.md) for cache eligibility and freshness controls. ## ORMs and query builders | Task | Official documentation | |------|------------------------| -| Use Drizzle with PostgreSQL | [PostgreSQL Drizzle guide](https://developers.cloudflare.com/hyperdrive/examples/connect-to-postgres/postgres-drivers-and-libraries/drizzle-orm/) | -| Use Drizzle with MySQL | [MySQL Drizzle guide](https://developers.cloudflare.com/hyperdrive/examples/connect-to-mysql/mysql-drivers-and-libraries/drizzle-orm/) | -| Use Prisma with PostgreSQL | [Prisma guide](https://developers.cloudflare.com/hyperdrive/examples/connect-to-postgres/postgres-drivers-and-libraries/prisma-orm/) | -| Assess another query builder, including Kysely | [Postgres.js integration notes](https://developers.cloudflare.com/hyperdrive/examples/connect-to-postgres/postgres-drivers-and-libraries/postgres-js/) and [database compatibility](https://developers.cloudflare.com/hyperdrive/reference/supported-databases-and-features/), then the library's current dialect documentation | +| Use Drizzle with PostgreSQL | [PostgreSQL Drizzle guide](https://developers.cloudflare.com/hyperdrive/examples/connect-to-postgres/postgres-drivers-and-libraries/drizzle-orm/index.md) | +| Use Drizzle with MySQL | [MySQL Drizzle guide](https://developers.cloudflare.com/hyperdrive/examples/connect-to-mysql/mysql-drivers-and-libraries/drizzle-orm/index.md) | +| Use Prisma with PostgreSQL | [Prisma guide](https://developers.cloudflare.com/hyperdrive/examples/connect-to-postgres/postgres-drivers-and-libraries/prisma-orm/index.md) | +| Assess another query builder, including Kysely | [Postgres.js integration notes](https://developers.cloudflare.com/hyperdrive/examples/connect-to-postgres/postgres-drivers-and-libraries/postgres-js/index.md) and [database compatibility](https://developers.cloudflare.com/hyperdrive/reference/supported-databases-and-features/index.md), then the library's current dialect documentation | -An ORM still uses a database driver and inherits its Worker connection constraints. Keep clients scoped to the invocation using [connection lifecycle](https://developers.cloudflare.com/hyperdrive/concepts/connection-lifecycle/). When a library owns SQL for authentication or other fresh reads, pass a client using a cache-disabled configuration as described in [query caching](https://developers.cloudflare.com/hyperdrive/concepts/query-caching/). +An ORM still uses a database driver and inherits its Worker connection constraints. Keep clients scoped to the invocation using [connection lifecycle](https://developers.cloudflare.com/hyperdrive/concepts/connection-lifecycle/index.md). When a library owns SQL for authentication or other fresh reads, pass a client using a cache-disabled configuration as described in [query caching](https://developers.cloudflare.com/hyperdrive/concepts/query-caching/index.md). diff --git a/external/develop/devops/cloudflare/references/hyperdrive/configuration.md b/external/develop/devops/cloudflare/references/hyperdrive/configuration.md index 4430765..7f10077 100644 --- a/external/develop/devops/cloudflare/references/hyperdrive/configuration.md +++ b/external/develop/devops/cloudflare/references/hyperdrive/configuration.md @@ -4,24 +4,31 @@ See [README.md](./README.md) for the retrieval workflow. Fetch the relevant guid | Task | Official documentation | |------|------------------------| -| Create the first configuration and bind it to a Worker | [Get started](https://developers.cloudflare.com/hyperdrive/get-started/) | -| Create, inspect, update, or delete configurations; set cache or pool options | [Wrangler commands](https://developers.cloudflare.com/hyperdrive/reference/wrangler-commands/) | -| Generate TypeScript types from Worker configuration | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/) | -| Connect a private database using the recommended Workers VPC route | [Workers VPC integration](https://developers.cloudflare.com/hyperdrive/configuration/connect-to-private-database-vpc/) | -| Maintain a private database connection using Tunnel and Access | [Tunnel integration](https://developers.cloudflare.com/hyperdrive/configuration/connect-to-private-database/) | -| Configure database network access | [Firewall and networking](https://developers.cloudflare.com/hyperdrive/configuration/firewall-and-networking-configuration/) | -| Configure server verification or client certificates | [SSL/TLS certificates](https://developers.cloudflare.com/hyperdrive/configuration/tls-ssl-certificates-for-hyperdrive/) | -| Rotate origin database credentials | [Credential rotation](https://developers.cloudflare.com/hyperdrive/configuration/rotate-credentials/) | -| Configure cache freshness or separate cached and fresh-read bindings | [Query caching](https://developers.cloudflare.com/hyperdrive/concepts/query-caching/) | -| Budget origin connections across configurations | [Tune connection pooling](https://developers.cloudflare.com/hyperdrive/configuration/tune-connection-pool/) | -| Choose local database access or remote Hyperdrive testing | [Local development](https://developers.cloudflare.com/hyperdrive/configuration/local-development/) | -| Evaluate Worker placement for multiple database round trips | [Smart Placement](https://developers.cloudflare.com/workers/configuration/placement/) | +| Create the first configuration and bind it to a Worker | [Get started](https://developers.cloudflare.com/hyperdrive/get-started/index.md) | +| Create, inspect, update, or delete configurations; set cache or pool options | [Wrangler commands](https://developers.cloudflare.com/hyperdrive/reference/wrangler-commands/index.md) | +| Generate TypeScript types from Worker configuration | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/index.md) | +| Connect a private database using the recommended Workers VPC route | [Workers VPC integration](https://developers.cloudflare.com/hyperdrive/configuration/connect-to-private-database-vpc/index.md) | +| Maintain a private database connection using Tunnel and Access | [Tunnel integration](https://developers.cloudflare.com/hyperdrive/configuration/connect-to-private-database/index.md) | +| Configure database network access | [Firewall and networking](https://developers.cloudflare.com/hyperdrive/configuration/firewall-and-networking-configuration/index.md) | +| Configure server verification or client certificates | [SSL/TLS certificates](https://developers.cloudflare.com/hyperdrive/configuration/tls-ssl-certificates-for-hyperdrive/index.md) | +| Rotate origin database credentials | [Credential rotation](https://developers.cloudflare.com/hyperdrive/configuration/rotate-credentials/index.md) | +| Configure cache freshness or separate cached and fresh-read bindings | [Query caching](https://developers.cloudflare.com/hyperdrive/concepts/query-caching/index.md) | +| Budget origin connections across configurations | [Tune connection pooling](https://developers.cloudflare.com/hyperdrive/configuration/tune-connection-pool/index.md) | +| Choose local database access or remote Hyperdrive testing | [Local development](https://developers.cloudflare.com/hyperdrive/configuration/local-development/index.md) | +| Place compute near a regional database | [Placement Hints](https://developers.cloudflare.com/workers/configuration/placement/index.md#configure-explicit-placement-hints) | ## Setup decisions -- Identify the database engine, provider, and network path first. The [PostgreSQL](https://developers.cloudflare.com/hyperdrive/examples/connect-to-postgres/) and [MySQL](https://developers.cloudflare.com/hyperdrive/examples/connect-to-mysql/) indexes route to provider-specific instructions. +- Identify the database engine, provider, and network path first. The [PostgreSQL](https://developers.cloudflare.com/hyperdrive/examples/connect-to-postgres/index.md) and [MySQL](https://developers.cloudflare.com/hyperdrive/examples/connect-to-mysql/index.md) indexes route to provider-specific instructions. - For private connectivity, choose Workers VPC or the existing Tunnel/Access integration before configuring credentials. Follow the selected guide's prerequisites and TLS guidance. - Decide which reads may be stale before selecting cache settings. Multiple configurations against one database contribute to its total origin connection usage. - Local direct database access does not exercise Hyperdrive pooling or caching. Use the local-development guide's remote option when verifying those behaviors, and identify the database that option targets before running writes. See [api.md](./api.md) for drivers and [gotchas.md](./gotchas.md) for diagnosis. + +## Place compute near the database + +Fetch [Workers placement](https://developers.cloudflare.com/workers/configuration/placement/index.md) before configuring a database-backed Worker. For multiple sequential queries to one regional database, prefer an explicit region hint over waiting for Smart Placement to learn from traffic. Hyperdrive pools origin connections; its binding does not automatically configure Worker placement. + +- Establish the database's actual cloud provider and region from provider metadata or the user. +- If location is missing, ask for the provider and region. For unknown or multiple back-end locations, use [Smart Placement](https://developers.cloudflare.com/workers/configuration/placement/index.md#enable-smart-placement). diff --git a/external/develop/devops/cloudflare/references/hyperdrive/gotchas.md b/external/develop/devops/cloudflare/references/hyperdrive/gotchas.md index af68a88..a4dd0ec 100644 --- a/external/develop/devops/cloudflare/references/hyperdrive/gotchas.md +++ b/external/develop/devops/cloudflare/references/hyperdrive/gotchas.md @@ -1,20 +1,20 @@ # Hyperdrive troubleshooting -Start with the actual error and the affected configuration. Fetch [Troubleshoot and debug](https://developers.cloudflare.com/hyperdrive/observability/troubleshooting/) for current error codes and diagnosis rather than guessing from a generic connection failure. +Start with the actual error and the affected configuration. Fetch [Troubleshoot and debug](https://developers.cloudflare.com/hyperdrive/observability/troubleshooting/index.md) for current error codes and diagnosis rather than guessing from a generic connection failure. | Symptom | What to inspect and where to read | |---------|----------------------------------| -| Connection refused or authentication failure | Check origin reachability and credentials using [troubleshooting](https://developers.cloudflare.com/hyperdrive/observability/troubleshooting/) and [firewall/networking configuration](https://developers.cloudflare.com/hyperdrive/configuration/firewall-and-networking-configuration/). | -| Private database or TLS failure | Follow the selected [Workers VPC](https://developers.cloudflare.com/hyperdrive/configuration/connect-to-private-database-vpc/) or [Tunnel/Access](https://developers.cloudflare.com/hyperdrive/configuration/connect-to-private-database/) path and its certificate prerequisites; see [SSL/TLS configuration](https://developers.cloudflare.com/hyperdrive/configuration/tls-ssl-certificates-for-hyperdrive/). | -| Pool exhaustion or too many connections | Distinguish client connection lifetime from origin pool capacity. Read [connection lifecycle](https://developers.cloudflare.com/hyperdrive/concepts/connection-lifecycle/), [pool tuning](https://developers.cloudflare.com/hyperdrive/configuration/tune-connection-pool/), and [limits](https://developers.cloudflare.com/hyperdrive/platform/limits/). | -| Query timeout | Check the current [limits](https://developers.cloudflare.com/hyperdrive/platform/limits/) and [metrics](https://developers.cloudflare.com/hyperdrive/observability/metrics/) before changing query or transaction design. | -| Stale reads or unexpectedly uncached queries | Inspect the binding's cache configuration and query eligibility in [query caching](https://developers.cloudflare.com/hyperdrive/concepts/query-caching/). Writes do not purge cached reads; do not treat prepared-statement settings as cache controls. | -| Slow multi-query requests | Inspect [metrics](https://developers.cloudflare.com/hyperdrive/observability/metrics/) and evaluate [Smart Placement](https://developers.cloudflare.com/workers/configuration/placement/). | -| Local connection failure, ignored environment variable, or absent cache behavior | Check binding names, local connection overrides, precedence, and remote testing in [local development](https://developers.cloudflare.com/hyperdrive/configuration/local-development/). | -| Unsupported driver or SQL feature | Check [supported databases and features](https://developers.cloudflare.com/hyperdrive/reference/supported-databases-and-features/) and the selected [driver guide](./api.md). | +| Connection refused or authentication failure | Check origin reachability and credentials using [troubleshooting](https://developers.cloudflare.com/hyperdrive/observability/troubleshooting/index.md) and [firewall/networking configuration](https://developers.cloudflare.com/hyperdrive/configuration/firewall-and-networking-configuration/index.md). | +| Private database or TLS failure | Follow the selected [Workers VPC](https://developers.cloudflare.com/hyperdrive/configuration/connect-to-private-database-vpc/index.md) or [Tunnel/Access](https://developers.cloudflare.com/hyperdrive/configuration/connect-to-private-database/index.md) path and its certificate prerequisites; see [SSL/TLS configuration](https://developers.cloudflare.com/hyperdrive/configuration/tls-ssl-certificates-for-hyperdrive/index.md). | +| Pool exhaustion or too many connections | Distinguish client connection lifetime from origin pool capacity. Read [connection lifecycle](https://developers.cloudflare.com/hyperdrive/concepts/connection-lifecycle/index.md), [pool tuning](https://developers.cloudflare.com/hyperdrive/configuration/tune-connection-pool/index.md), and [limits](https://developers.cloudflare.com/hyperdrive/platform/limits/index.md). | +| Query timeout | Check the current [limits](https://developers.cloudflare.com/hyperdrive/platform/limits/index.md) and [metrics](https://developers.cloudflare.com/hyperdrive/observability/metrics/index.md) before changing query or transaction design. | +| Stale reads or unexpectedly uncached queries | Inspect the binding's cache configuration and query eligibility in [query caching](https://developers.cloudflare.com/hyperdrive/concepts/query-caching/index.md). Writes do not purge cached reads; do not treat prepared-statement settings as cache controls. | +| Slow multi-query requests | Inspect [metrics](https://developers.cloudflare.com/hyperdrive/observability/metrics/index.md) and check whether Worker placement matches the regional database. Follow [placement setup](./configuration.md#place-compute-near-the-database); a Hyperdrive binding alone does not place compute near the origin. | +| Local connection failure, ignored environment variable, or absent cache behavior | Check binding names, local connection overrides, precedence, and remote testing in [local development](https://developers.cloudflare.com/hyperdrive/configuration/local-development/index.md). | +| Unsupported driver or SQL feature | Check [supported databases and features](https://developers.cloudflare.com/hyperdrive/reference/supported-databases-and-features/index.md) and the selected [driver guide](./api.md). | ## Capacity and changes -Retrieve [limits](https://developers.cloudflare.com/hyperdrive/platform/limits/) and [pricing](https://developers.cloudflare.com/hyperdrive/platform/pricing/) for current plan allowances, connection and query bounds, and limit-increase guidance. Check [release notes](https://developers.cloudflare.com/hyperdrive/platform/release-notes/) when behavior changes after an upgrade. +Retrieve [limits](https://developers.cloudflare.com/hyperdrive/platform/limits/index.md) and [pricing](https://developers.cloudflare.com/hyperdrive/platform/pricing/index.md) for current plan allowances, connection and query bounds, and limit-increase guidance. Check [release notes](https://developers.cloudflare.com/hyperdrive/platform/release-notes/index.md) when behavior changes after an upgrade. See [configuration.md](./configuration.md) to change a configuration and [patterns.md](./patterns.md) to revisit freshness or connection decisions. diff --git a/external/develop/devops/cloudflare/references/hyperdrive/patterns.md b/external/develop/devops/cloudflare/references/hyperdrive/patterns.md index 8fb6958..9bc347b 100644 --- a/external/develop/devops/cloudflare/references/hyperdrive/patterns.md +++ b/external/develop/devops/cloudflare/references/hyperdrive/patterns.md @@ -4,12 +4,12 @@ See [api.md](./api.md) for maintained driver and ORM examples. Use the following | Workload or decision | Guidance and documentation | |----------------------|----------------------------| -| Popular content or analytics dashboards | Cache only when the product can tolerate the configured stale window. Use [query caching](https://developers.cloudflare.com/hyperdrive/concepts/query-caching/) for eligibility, parameters, and settings. | -| Mixed cached reads and fresh reads | Route authentication, permissions, and reads after writes through a cache-disabled configuration. Writes do not invalidate cached results; see [read-after-write behavior](https://developers.cloudflare.com/hyperdrive/concepts/query-caching/#read-after-write-behavior). | -| Multi-tenant queries | Derive tenant scope from authenticated application context and apply it to every query. A cache is not an authorization boundary. Review [query caching](https://developers.cloudflare.com/hyperdrive/concepts/query-caching/) for the selected query's behavior. | -| Globally distributed callers | Understand the distinction between fast connection setup and the remaining query round trip in [how Hyperdrive works](https://developers.cloudflare.com/hyperdrive/concepts/how-hyperdrive-works/). | -| Multiple sequential database queries | Measure placement rather than assuming the nearest user location is best. Consult [Smart Placement](https://developers.cloudflare.com/workers/configuration/placement/) and [Hyperdrive metrics](https://developers.cloudflare.com/hyperdrive/observability/metrics/). | -| Transactions or connection-local state | Keep transactions short and do not assume state survives across transactions. Fetch [connection pooling](https://developers.cloudflare.com/hyperdrive/concepts/connection-pooling/) and [supported features](https://developers.cloudflare.com/hyperdrive/reference/supported-databases-and-features/) before relying on session settings. | -| Client lifetime and pool sizing | Create clients per handler invocation; Hyperdrive owns the origin pool. Use [connection lifecycle](https://developers.cloudflare.com/hyperdrive/concepts/connection-lifecycle/) and [pool tuning](https://developers.cloudflare.com/hyperdrive/configuration/tune-connection-pool/) instead of a global driver pool or copied connection counts. | +| Popular content or analytics dashboards | Cache only when the product can tolerate the configured stale window. Use [query caching](https://developers.cloudflare.com/hyperdrive/concepts/query-caching/index.md) for eligibility, parameters, and settings. | +| Mixed cached reads and fresh reads | Route authentication, permissions, and reads after writes through a cache-disabled configuration. Writes do not invalidate cached results; see [read-after-write behavior](https://developers.cloudflare.com/hyperdrive/concepts/query-caching/index.md#read-after-write-behavior). | +| Multi-tenant queries | Derive tenant scope from authenticated application context and apply it to every query. A cache is not an authorization boundary. Review [query caching](https://developers.cloudflare.com/hyperdrive/concepts/query-caching/index.md) for the selected query's behavior. | +| Globally distributed callers | Understand the distinction between fast connection setup and the remaining query round trip in [how Hyperdrive works](https://developers.cloudflare.com/hyperdrive/concepts/how-hyperdrive-works/index.md). | +| Multiple sequential database queries | For one regional database, configure an explicit region hint using [placement setup](./configuration.md#place-compute-near-the-database). Compare deployed request duration and [Hyperdrive metrics](https://developers.cloudflare.com/hyperdrive/observability/metrics/index.md) with representative cache behavior. | +| Transactions or connection-local state | Keep transactions short and do not assume state survives across transactions. Fetch [connection pooling](https://developers.cloudflare.com/hyperdrive/concepts/connection-pooling/index.md) and [supported features](https://developers.cloudflare.com/hyperdrive/reference/supported-databases-and-features/index.md) before relying on session settings. | +| Client lifetime and pool sizing | Create clients per handler invocation; Hyperdrive owns the origin pool. Use [connection lifecycle](https://developers.cloudflare.com/hyperdrive/concepts/connection-lifecycle/index.md) and [pool tuning](https://developers.cloudflare.com/hyperdrive/configuration/tune-connection-pool/index.md) instead of a global driver pool or copied connection counts. | Separate application correctness from acceleration: use parameterized queries, enforce tenant access in the application, and select freshness before tuning cache hit rate. See [gotchas.md](./gotchas.md) when observed behavior differs from the design. diff --git a/external/develop/devops/cloudflare/references/images/api.md b/external/develop/devops/cloudflare/references/images/api.md index 7387c54..3ae1c3f 100644 --- a/external/develop/devops/cloudflare/references/images/api.md +++ b/external/develop/devops/cloudflare/references/images/api.md @@ -2,12 +2,12 @@ | Operation | Documentation | |-----------|---------------| -| Optimize image bytes in a Worker; select input, transform, output, and response methods | [Optimize with Workers](https://developers.cloudflare.com/images/optimization/binding/#methods) | -| Upload, list, retrieve, update, or delete hosted images from a Worker | [Manage hosted images with Workers](https://developers.cloudflare.com/images/storage/binding/) | -| Upload or manage images through HTTP | [Upload methods](https://developers.cloudflare.com/images/storage/upload-images/methods/#upload-using-api) and its linked Images API reference | -| Accept uploads directly from a client | [Direct Creator Upload](https://developers.cloudflare.com/images/storage/upload-images/direct-creator-upload/) | -| Construct hosted-image delivery URLs | [Serve uploaded images](https://developers.cloudflare.com/images/optimization/hosted-images/serve-uploaded-images/) | -| Apply URL optimization parameters or select fit, quality, and format | [Optimization features](https://developers.cloudflare.com/images/optimization/features/) | -| Draw overlays or watermarks | [Draw overlays](https://developers.cloudflare.com/images/optimization/draw-overlays/) | +| Optimize image bytes in a Worker; select input, transform, output, and response methods | [Optimize with Workers](https://developers.cloudflare.com/images/optimization/binding/index.md#methods) | +| Upload, list, retrieve, update, or delete hosted images from a Worker | [Manage hosted images with Workers](https://developers.cloudflare.com/images/storage/binding/index.md) | +| Upload or manage images through HTTP | [Upload methods](https://developers.cloudflare.com/images/storage/upload-images/methods/index.md#upload-using-api) and its linked Images API reference | +| Accept uploads directly from a client | [Direct Creator Upload](https://developers.cloudflare.com/images/storage/upload-images/direct-creator-upload/index.md) | +| Construct hosted-image delivery URLs | [Serve uploaded images](https://developers.cloudflare.com/images/optimization/hosted-images/serve-uploaded-images/index.md) | +| Apply URL optimization parameters or select fit, quality, and format | [Optimization features](https://developers.cloudflare.com/images/optimization/features/index.md) | +| Draw overlays or watermarks | [Draw overlays](https://developers.cloudflare.com/images/optimization/draw-overlays/index.md) | Do not transfer URL parameters or HTTP request shapes directly into binding calls. Read the contract for the selected interface, including output format handling. Use the project's generated binding types and existing error handling. See [configuration](configuration.md) for setup and [troubleshooting](gotchas.md) for failures and limits. diff --git a/external/develop/devops/cloudflare/references/images/configuration.md b/external/develop/devops/cloudflare/references/images/configuration.md index 908eb6c..8892f11 100644 --- a/external/develop/devops/cloudflare/references/images/configuration.md +++ b/external/develop/devops/cloudflare/references/images/configuration.md @@ -4,14 +4,14 @@ Inspect the project's Wrangler configuration, dependency versions, existing bind | Task | Documentation | |------|---------------| -| Add the optimization binding | [Binding setup](https://developers.cloudflare.com/images/optimization/binding/#setup) | -| Configure hosted-image management in a Worker | [Hosted binding setup](https://developers.cloudflare.com/images/storage/binding/#setup) | -| Choose local or remote development for the optimization binding | [Local binding development](https://developers.cloudflare.com/images/optimization/binding/#interact-with-your-images-binding-locally) | -| Upload through the dashboard or API | [Upload methods](https://developers.cloudflare.com/images/storage/upload-images/methods/) | -| Create named presets for hosted images | [Create predefined variants](https://developers.cloudflare.com/images/optimization/hosted-images/create-variants/) | -| Enable dynamic options for hosted-image URLs | [Enable flexible variants](https://developers.cloudflare.com/images/optimization/hosted-images/enable-flexible-variants/) | -| Find account hash and delivery URL components | [Serve uploaded images](https://developers.cloudflare.com/images/optimization/hosted-images/serve-uploaded-images/) | -| Configure private access and generate signed URLs | [Serve private images](https://developers.cloudflare.com/images/optimization/hosted-images/serve-private-images/) | -| Set hosted-image cache lifetime | [Browser TTL](https://developers.cloudflare.com/images/optimization/hosted-images/browser-ttl/) | +| Add the optimization binding | [Binding setup](https://developers.cloudflare.com/images/optimization/binding/index.md#setup) | +| Configure hosted-image management in a Worker | [Hosted binding setup](https://developers.cloudflare.com/images/storage/binding/index.md#setup) | +| Choose local or remote development for the optimization binding | [Local binding development](https://developers.cloudflare.com/images/optimization/binding/index.md#interact-with-your-images-binding-locally) | +| Upload through the dashboard or API | [Upload methods](https://developers.cloudflare.com/images/storage/upload-images/methods/index.md) | +| Create named presets for hosted images | [Create predefined variants](https://developers.cloudflare.com/images/optimization/hosted-images/create-variants/index.md) | +| Enable dynamic options for hosted-image URLs | [Enable flexible variants](https://developers.cloudflare.com/images/optimization/hosted-images/enable-flexible-variants/index.md) | +| Find account hash and delivery URL components | [Serve uploaded images](https://developers.cloudflare.com/images/optimization/hosted-images/serve-uploaded-images/index.md) | +| Configure private access and generate signed URLs | [Serve private images](https://developers.cloudflare.com/images/optimization/hosted-images/serve-private-images/index.md) | +| Set hosted-image cache lifetime | [Browser TTL](https://developers.cloudflare.com/images/optimization/hosted-images/browser-ttl/index.md) | Keep API tokens and signing keys in the project's secret mechanism. Verify private-delivery requirements when choosing variants, and follow the documented signing procedure rather than maintaining a custom signing recipe here. Confirm that the selected local test mode covers the features being changed. Continue with [API selection](api.md) or [patterns](patterns.md). diff --git a/external/develop/devops/cloudflare/references/images/gotchas.md b/external/develop/devops/cloudflare/references/images/gotchas.md index 6e96582..7701607 100644 --- a/external/develop/devops/cloudflare/references/images/gotchas.md +++ b/external/develop/devops/cloudflare/references/images/gotchas.md @@ -4,13 +4,13 @@ First identify whether the failure involves hosted-image storage, remote URL tra | Symptom or question | Documentation | |---------------------|---------------| -| Resizing is absent, an origin request fails, or a transformation returns an error code | [Troubleshooting](https://developers.cloudflare.com/images/reference/troubleshooting/) | -| Input size, dimensions, animation, or format compatibility | [Limits and formats](https://developers.cloudflare.com/images/get-started/limits/) — choose the section for the affected interface | -| Unexpected fit, quality, format, or crop behavior | [Optimization features](https://developers.cloudflare.com/images/optimization/features/) | -| Binding input, output, or response handling fails | [Binding methods](https://developers.cloudflare.com/images/optimization/binding/#methods) | -| Local behavior differs from production | [Local binding development](https://developers.cloudflare.com/images/optimization/binding/#interact-with-your-images-binding-locally) | -| Private delivery fails or an image is unexpectedly public | [Serve private images](https://developers.cloudflare.com/images/optimization/hosted-images/serve-private-images/) and [variant public access](https://developers.cloudflare.com/images/optimization/hosted-images/create-variants/#public-access) | -| Remote transformations appear stale | [Caching and purging](https://developers.cloudflare.com/images/reference/troubleshooting/#caching-and-purging) | -| Worker transformations repeat unnecessarily | [Binding caching guidance](https://developers.cloudflare.com/images/optimization/binding/#methods) | +| Resizing is absent, an origin request fails, or a transformation returns an error code | [Troubleshooting](https://developers.cloudflare.com/images/reference/troubleshooting/index.md) | +| Input size, dimensions, animation, or format compatibility | [Limits and formats](https://developers.cloudflare.com/images/get-started/limits/index.md) — choose the section for the affected interface | +| Unexpected fit, quality, format, or crop behavior | [Optimization features](https://developers.cloudflare.com/images/optimization/features/index.md) | +| Binding input, output, or response handling fails | [Binding methods](https://developers.cloudflare.com/images/optimization/binding/index.md#methods) | +| Local behavior differs from production | [Local binding development](https://developers.cloudflare.com/images/optimization/binding/index.md#interact-with-your-images-binding-locally) | +| Private delivery fails or an image is unexpectedly public | [Serve private images](https://developers.cloudflare.com/images/optimization/hosted-images/serve-private-images/index.md) and [variant public access](https://developers.cloudflare.com/images/optimization/hosted-images/create-variants/index.md#public-access) | +| Remote transformations appear stale | [Caching and purging](https://developers.cloudflare.com/images/reference/troubleshooting/index.md#caching-and-purging) | +| Worker transformations repeat unnecessarily | [Binding caching guidance](https://developers.cloudflare.com/images/optimization/binding/index.md#methods) | Do not apply one interface's limits, error codes, or caching rules to another. Reproduce with a representative image and verify the chosen fix using the project's existing checks. Retry only after identifying a transient failure; changing invalid inputs or access configuration requires a different fix. See [API selection](api.md) and [configuration](configuration.md) when the wrong interface or setup is responsible. diff --git a/external/develop/devops/cloudflare/references/images/patterns.md b/external/develop/devops/cloudflare/references/images/patterns.md index fdfb6ef..8192b6f 100644 --- a/external/develop/devops/cloudflare/references/images/patterns.md +++ b/external/develop/devops/cloudflare/references/images/patterns.md @@ -4,13 +4,13 @@ Choose the workflow that matches the existing storage and delivery architecture, | Workflow | Documentation | |----------|---------------| -| Let users upload without exposing account credentials | [Direct Creator Upload](https://developers.cloudflare.com/images/storage/upload-images/direct-creator-upload/) | -| Serve images for different layouts and display densities | [Make responsive images](https://developers.cloudflare.com/images/optimization/make-responsive-images/) | -| Select output format for hosted images | [Hosted-image format optimization](https://developers.cloudflare.com/images/optimization/hosted-images/serve-uploaded-images/#optimize-format) | -| Select output format for a Worker pipeline | [Workers optimization binding](https://developers.cloudflare.com/images/optimization/binding/) | -| Optimize user uploads, add a watermark, and store the result in R2 | [Transform user-uploaded images before uploading to R2](https://developers.cloudflare.com/images/tutorials/optimize-user-uploaded-image/) | -| Compose overlays and watermarks | [Draw overlays](https://developers.cloudflare.com/images/optimization/draw-overlays/) | -| Cache a Worker transformation response | [Binding methods and caching guidance](https://developers.cloudflare.com/images/optimization/binding/#methods) | -| Configure hosted-image browser caching | [Browser TTL](https://developers.cloudflare.com/images/optimization/hosted-images/browser-ttl/) | +| Let users upload without exposing account credentials | [Direct Creator Upload](https://developers.cloudflare.com/images/storage/upload-images/direct-creator-upload/index.md) | +| Serve images for different layouts and display densities | [Make responsive images](https://developers.cloudflare.com/images/optimization/make-responsive-images/index.md) | +| Select output format for hosted images | [Hosted-image format optimization](https://developers.cloudflare.com/images/optimization/hosted-images/serve-uploaded-images/index.md#optimize-format) | +| Select output format for a Worker pipeline | [Workers optimization binding](https://developers.cloudflare.com/images/optimization/binding/index.md) | +| Optimize user uploads, add a watermark, and store the result in R2 | [Transform user-uploaded images before uploading to R2](https://developers.cloudflare.com/images/tutorials/optimize-user-uploaded-image/index.md) | +| Compose overlays and watermarks | [Draw overlays](https://developers.cloudflare.com/images/optimization/draw-overlays/index.md) | +| Cache a Worker transformation response | [Binding methods and caching guidance](https://developers.cloudflare.com/images/optimization/binding/index.md#methods) | +| Configure hosted-image browser caching | [Browser TTL](https://developers.cloudflare.com/images/optimization/hosted-images/browser-ttl/index.md) | Adapt dimensions and quality to the actual layout and representative source images. Keep upload credentials server-side, preserve the application's access checks, and validate both the resulting image and its response headers. Consult [limits and troubleshooting](gotchas.md) before choosing batch sizes or retry behavior. diff --git a/external/develop/devops/cloudflare/references/kv/README.md b/external/develop/devops/cloudflare/references/kv/README.md index b246645..5cbe818 100644 --- a/external/develop/devops/cloudflare/references/kv/README.md +++ b/external/develop/devops/cloudflare/references/kv/README.md @@ -1,14 +1,14 @@ # Cloudflare Workers KV -Use KV for read-heavy configuration, preferences, and application caches that tolerate stale data. Read [how KV works](https://developers.cloudflare.com/kv/concepts/how-kv-works/) before choosing it: reads are eventually consistent, including cached missing keys, and immediate visibility is not guaranteed even in the location of a write. +Use KV for read-heavy configuration, preferences, and application caches that tolerate stale data. Read [how KV works](https://developers.cloudflare.com/kv/concepts/how-kv-works/index.md) before choosing it: reads are eventually consistent, including cached missing keys, and immediate visibility is not guaranteed even in the location of a write. -For atomic updates or coordination, consider [Durable Objects](https://developers.cloudflare.com/durable-objects/); for relational queries, [D1](../d1/); for large objects, [R2](../r2/). Use the [storage comparison](https://developers.cloudflare.com/workers/platform/storage-options/) to choose based on requirements. +For atomic updates or coordination, consider [Durable Objects](https://developers.cloudflare.com/durable-objects/index.md); for relational queries, [D1](../d1/); for large objects, [R2](../r2/). Use the [storage comparison](https://developers.cloudflare.com/workers/platform/storage-options/index.md) to choose based on requirements. Read the current documentation for the task before implementing. Use the [KV documentation index](https://developers.cloudflare.com/kv/llms.txt) to discover additional guides; these files preserve task routes rather than copies of APIs, commands, or numeric limits. ## Start here -- [Get started](https://developers.cloudflare.com/kv/get-started/): create a namespace, bind it, and read and write data. +- [Get started](https://developers.cloudflare.com/kv/get-started/index.md): create a namespace, bind it, and read and write data. - [configuration.md](./configuration.md): bindings, environments, types, local development, CLI, and REST access. - [api.md](./api.md): reads, writes, metadata, deletion, bulk operations, and pagination. - [patterns.md](./patterns.md): caching, sessions, key design, versioning, and fallback decisions. diff --git a/external/develop/devops/cloudflare/references/kv/api.md b/external/develop/devops/cloudflare/references/kv/api.md index 4582cae..bc62d64 100644 --- a/external/develop/devops/cloudflare/references/kv/api.md +++ b/external/develop/devops/cloudflare/references/kv/api.md @@ -4,12 +4,12 @@ Read the relevant API page before implementing; it defines current options, resu | Task | Documentation | |------|---------------| -| Read one or several keys; choose text, JSON, binary, or stream results | [Read key-value pairs](https://developers.cloudflare.com/kv/api/read-key-value-pairs/) | -| Read metadata with values; tune read caching or coalesce related keys | [Read guidance](https://developers.cloudflare.com/kv/api/read-key-value-pairs/) | -| Write values and metadata; set absolute expiration or a relative lifetime | [Write key-value pairs](https://developers.cloudflare.com/kv/api/write-key-value-pairs/) | -| Delete a key | [Delete key-value pairs](https://developers.cloudflare.com/kv/api/delete-key-value-pairs/) | -| Enumerate keys, filter by prefix, and paginate | [List keys](https://developers.cloudflare.com/kv/api/list-keys/) | -| Access namespaces or perform bulk operations outside a Worker | [KV REST API](https://developers.cloudflare.com/api/resources/kv/) and [Wrangler KV commands](https://developers.cloudflare.com/kv/reference/kv-commands/) | +| Read one or several keys; choose text, JSON, binary, or stream results | [Read key-value pairs](https://developers.cloudflare.com/kv/api/read-key-value-pairs/index.md) | +| Read metadata with values; tune read caching or coalesce related keys | [Read guidance](https://developers.cloudflare.com/kv/api/read-key-value-pairs/index.md) | +| Write values and metadata; set absolute expiration or a relative lifetime | [Write key-value pairs](https://developers.cloudflare.com/kv/api/write-key-value-pairs/index.md) | +| Delete a key | [Delete key-value pairs](https://developers.cloudflare.com/kv/api/delete-key-value-pairs/index.md) | +| Enumerate keys, filter by prefix, and paginate | [List keys](https://developers.cloudflare.com/kv/api/list-keys/index.md) | +| Access namespaces or perform bulk operations outside a Worker | [KV REST API](https://developers.cloudflare.com/api/resources/kv/index.md) and [Wrangler KV commands](https://developers.cloudflare.com/kv/reference/kv-commands/index.md) | Handle missing values explicitly: JavaScript reads return `null` for absent keys; valid stored values can be falsy. Choose defaults separately from how you handle request failures. diff --git a/external/develop/devops/cloudflare/references/kv/configuration.md b/external/develop/devops/cloudflare/references/kv/configuration.md index c83b6d8..a2da705 100644 --- a/external/develop/devops/cloudflare/references/kv/configuration.md +++ b/external/develop/devops/cloudflare/references/kv/configuration.md @@ -4,12 +4,12 @@ Read the setup guide for the target environment before creating resources or edi | Task | Documentation | |------|---------------| -| Create a namespace and connect a Worker | [Get started](https://developers.cloudflare.com/kv/get-started/) and [KV bindings](https://developers.cloudflare.com/kv/concepts/kv-bindings/) | -| Configure staging and production namespaces | [KV environments](https://developers.cloudflare.com/kv/reference/environments/) | -| Generate Worker environment and binding types | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/) | -| Develop against local storage or a remote binding | [KV local development](https://developers.cloudflare.com/kv/concepts/kv-bindings/) and [remote bindings](https://developers.cloudflare.com/workers/local-development/#remote-bindings) | -| Manage namespaces, individual keys, and bulk files from the CLI | [Wrangler KV commands](https://developers.cloudflare.com/kv/reference/kv-commands/) | -| Manage KV from another service or SDK | [KV REST API](https://developers.cloudflare.com/api/resources/kv/) | +| Create a namespace and connect a Worker | [Get started](https://developers.cloudflare.com/kv/get-started/index.md) and [KV bindings](https://developers.cloudflare.com/kv/concepts/kv-bindings/index.md) | +| Configure staging and production namespaces | [KV environments](https://developers.cloudflare.com/kv/reference/environments/index.md) | +| Generate Worker environment and binding types | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/index.md) | +| Develop against local storage or a remote binding | [KV local development](https://developers.cloudflare.com/kv/concepts/kv-bindings/index.md) and [remote bindings](https://developers.cloudflare.com/workers/local-development/index.md#remote-bindings) | +| Manage namespaces, individual keys, and bulk files from the CLI | [Wrangler KV commands](https://developers.cloudflare.com/kv/reference/kv-commands/index.md) | +| Manage KV from another service or SDK | [KV REST API](https://developers.cloudflare.com/api/resources/kv/index.md) | Choose the namespace, account, and environment deliberately. Local KV data is separate from remote data; a remote binding accesses the selected Cloudflare namespace even when Worker code runs locally. Check the command's local/remote options and environment selection before seeding or inspecting data. A separate preview namespace is not required simply to use local KV. diff --git a/external/develop/devops/cloudflare/references/kv/gotchas.md b/external/develop/devops/cloudflare/references/kv/gotchas.md index 7a489e6..e202dcf 100644 --- a/external/develop/devops/cloudflare/references/kv/gotchas.md +++ b/external/develop/devops/cloudflare/references/kv/gotchas.md @@ -4,14 +4,14 @@ Read the linked explanation before applying a workaround. | Symptom or decision | Documentation and guidance | |---------------------|----------------------------| -| Stale value after a write or delete | [How KV works](https://developers.cloudflare.com/kv/concepts/how-kv-works/): allow for eventual consistency; neither local read-after-write visibility nor a fixed global propagation deadline is guaranteed. | -| Newly created key still appears absent | [Read caching](https://developers.cloudflare.com/kv/api/read-key-value-pairs/): missing-key lookups are cached too. Treat check-then-create as a race, not an atomic existence test. | -| Concurrent updates overwrite each other or writes are throttled | [Concurrent writes](https://developers.cloudflare.com/kv/api/write-key-value-pairs/#concurrent-writes-to-the-same-key): retries do not make read-modify-write atomic. Use coordination when correctness depends on ordering. | -| Missing-value errors | [Read results](https://developers.cloudflare.com/kv/api/read-key-value-pairs/): distinguish `null` from valid falsy values and distinguish absence from an operation failure. | -| Slow reads, large results, or excessive operations | [Read guidance](https://developers.cloudflare.com/kv/api/read-key-value-pairs/): select result types and bulk reads to match the workload; increasing read cache lifetime trades freshness for cache reuse. | -| Unexpected empty listing page | [Pagination](https://developers.cloudflare.com/kv/api/list-keys/): use the completion flag and cursor, not page length, to determine whether to continue. | -| Data present in one environment but missing in another | [KV bindings](https://developers.cloudflare.com/kv/concepts/kv-bindings/) and [environments](https://developers.cloudflare.com/kv/reference/environments/): check local versus remote storage and the selected namespace. | -| Size, operation, or write-rate failures | [Limits](https://developers.cloudflare.com/kv/platform/limits/): retrieve current constraints before sizing values, batches, or retry policies. | -| Estimate costs or explain billing | [Pricing](https://developers.cloudflare.com/kv/platform/pricing/): check allowances, billable operations, storage, and bulk accounting for the actual workload. | +| Stale value after a write or delete | [How KV works](https://developers.cloudflare.com/kv/concepts/how-kv-works/index.md): allow for eventual consistency; neither local read-after-write visibility nor a fixed global propagation deadline is guaranteed. | +| Newly created key still appears absent | [Read caching](https://developers.cloudflare.com/kv/api/read-key-value-pairs/index.md): missing-key lookups are cached too. Treat check-then-create as a race, not an atomic existence test. | +| Concurrent updates overwrite each other or writes are throttled | [Concurrent writes](https://developers.cloudflare.com/kv/api/write-key-value-pairs/index.md#concurrent-writes-to-the-same-key): retries do not make read-modify-write atomic. Use coordination when correctness depends on ordering. | +| Missing-value errors | [Read results](https://developers.cloudflare.com/kv/api/read-key-value-pairs/index.md): distinguish `null` from valid falsy values and distinguish absence from an operation failure. | +| Slow reads, large results, or excessive operations | [Read guidance](https://developers.cloudflare.com/kv/api/read-key-value-pairs/index.md): select result types and bulk reads to match the workload; increasing read cache lifetime trades freshness for cache reuse. | +| Unexpected empty listing page | [Pagination](https://developers.cloudflare.com/kv/api/list-keys/index.md): use the completion flag and cursor, not page length, to determine whether to continue. | +| Data present in one environment but missing in another | [KV bindings](https://developers.cloudflare.com/kv/concepts/kv-bindings/index.md) and [environments](https://developers.cloudflare.com/kv/reference/environments/index.md): check local versus remote storage and the selected namespace. | +| Size, operation, or write-rate failures | [Limits](https://developers.cloudflare.com/kv/platform/limits/index.md): retrieve current constraints before sizing values, batches, or retry policies. | +| Estimate costs or explain billing | [Pricing](https://developers.cloudflare.com/kv/platform/pricing/index.md): check allowances, billable operations, storage, and bulk accounting for the actual workload. | Confirm freshness and failure requirements before adding a cache or a permissive fallback; see [patterns.md](./patterns.md). diff --git a/external/develop/devops/cloudflare/references/kv/patterns.md b/external/develop/devops/cloudflare/references/kv/patterns.md index 8cdb5c3..9a11ade 100644 --- a/external/develop/devops/cloudflare/references/kv/patterns.md +++ b/external/develop/devops/cloudflare/references/kv/patterns.md @@ -1,21 +1,21 @@ # KV Patterns & Best Practices -Read the guide for the pattern before implementing it, and confirm that [KV's consistency model](https://developers.cloudflare.com/kv/concepts/how-kv-works/) fits the application. +Read the guide for the pattern before implementing it, and confirm that [KV's consistency model](https://developers.cloudflare.com/kv/concepts/how-kv-works/index.md) fits the application. | Task | Documentation and design decision | |------|-----------------------------------| -| Cache application data or API results | [Cache data with KV](https://developers.cloudflare.com/kv/examples/cache-data-with-workers-kv/): decide acceptable staleness, expiration, and behavior when the origin fails. | -| Cache eligible HTTP responses | [Workers Cache](https://developers.cloudflare.com/workers/cache/): choose the HTTP caching mechanism based on response semantics. | -| Store configuration or feature flags | [Distributed configuration](https://developers.cloudflare.com/kv/examples/distributed-configuration-with-workers-kv/): choose defaults and rollout behavior that tolerate delayed updates. | -| Coalesce related keys | [Read guidance](https://developers.cloudflare.com/kv/api/read-key-value-pairs/): fewer reads can improve cache reuse, but combined values couple updates and can introduce write races. | -| Organize and enumerate keys by prefix | [List keys](https://developers.cloudflare.com/kv/api/list-keys/): use a consistent naming scheme and paginate every listing. | -| Attach schema versions or other metadata | [Write metadata](https://developers.cloudflare.com/kv/api/write-key-value-pairs/) and [read metadata](https://developers.cloudflare.com/kv/api/read-key-value-pairs/): define compatibility and migration behavior for older records; migrations must account for concurrent writes. | +| Cache application data or API results | [Cache data with KV](https://developers.cloudflare.com/kv/examples/cache-data-with-workers-kv/index.md): decide acceptable staleness, expiration, and behavior when the origin fails. | +| Cache eligible HTTP responses | [Workers Cache](https://developers.cloudflare.com/workers/cache/index.md): choose the HTTP caching mechanism based on response semantics. | +| Store configuration or feature flags | [Distributed configuration](https://developers.cloudflare.com/kv/examples/distributed-configuration-with-workers-kv/index.md): choose defaults and rollout behavior that tolerate delayed updates. | +| Coalesce related keys | [Read guidance](https://developers.cloudflare.com/kv/api/read-key-value-pairs/index.md): fewer reads can improve cache reuse, but combined values couple updates and can introduce write races. | +| Organize and enumerate keys by prefix | [List keys](https://developers.cloudflare.com/kv/api/list-keys/index.md): use a consistent naming scheme and paginate every listing. | +| Attach schema versions or other metadata | [Write metadata](https://developers.cloudflare.com/kv/api/write-key-value-pairs/index.md) and [read metadata](https://developers.cloudflare.com/kv/api/read-key-value-pairs/index.md): define compatibility and migration behavior for older records; migrations must account for concurrent writes. | ## Application-specific decisions The linked APIs are building blocks, not complete session or multi-tier cache implementations. Preserve these requirements when designing an application: - For a memory → KV → origin cache, define each layer's lifetime and refill behavior. Process memory is not shared durable state; KV adds its own stale-value and negative-lookup caching. -- For sessions, decide how quickly creation, updates, and revocation must become visible. KV alone cannot provide immediate global revocation or guaranteed immediate reads after session creation. Use a store with suitable consistency when those are requirements, and define application expiration checks using the [write expiration guidance](https://developers.cloudflare.com/kv/api/write-key-value-pairs/). -- For counters, rate limits, or other atomic read-modify-write decisions, use coordination such as [Durable Objects](https://developers.cloudflare.com/durable-objects/). Serializing writes through an object does not make separate KV reads strongly consistent. +- For sessions, decide how quickly creation, updates, and revocation must become visible. KV alone cannot provide immediate global revocation or guaranteed immediate reads after session creation. Use a store with suitable consistency when those are requirements, and define application expiration checks using the [write expiration guidance](https://developers.cloudflare.com/kv/api/write-key-value-pairs/index.md). +- For counters, rate limits, or other atomic read-modify-write decisions, use coordination such as [Durable Objects](https://developers.cloudflare.com/durable-objects/index.md). Serializing writes through an object does not make separate KV reads strongly consistent. - Choose missing-data defaults separately from service-error handling. A fallback appropriate for display preferences may be inappropriate for authorization or session validation. diff --git a/external/develop/devops/cloudflare/references/miniflare/README.md b/external/develop/devops/cloudflare/references/miniflare/README.md index 87e3861..dd074ed 100644 --- a/external/develop/devops/cloudflare/references/miniflare/README.md +++ b/external/develop/devops/cloudflare/references/miniflare/README.md @@ -6,16 +6,16 @@ Miniflare provides programmatic control of local Workers simulation. Read the li | Need | Start here | |------|------------| -| Unit tests that execute in the Workers runtime | [Workers Vitest setup](https://developers.cloudflare.com/workers/testing/vitest-integration/write-your-first-test/) | -| Integration tests against built Workers | [Integration test harness](https://developers.cloudflare.com/workers/testing/test-harness/) | -| Low-level simulator control for a custom harness | [Miniflare testing guide](https://developers.cloudflare.com/workers/testing/miniflare/writing-tests/) | -| Binding access from a Node.js process | [Wrangler getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy) | +| Unit tests that execute in the Workers runtime | [Workers Vitest setup](https://developers.cloudflare.com/workers/testing/vitest-integration/write-your-first-test/index.md) | +| Integration tests against built Workers | [Integration test harness](https://developers.cloudflare.com/workers/testing/test-harness/index.md) | +| Low-level simulator control for a custom harness | [Miniflare testing guide](https://developers.cloudflare.com/workers/testing/miniflare/writing-tests/index.md) | +| Binding access from a Node.js process | [Wrangler getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/index.md#getplatformproxy) | For interactive local development, use the project's Wrangler or Cloudflare Vite workflow. Direct Miniflare is useful when the higher-level testing tools do not expose the control needed. ## Read for the task -- [Get started](https://developers.cloudflare.com/workers/testing/miniflare/get-started/) — installation, scripts, lifecycle, and event dispatch. +- [Get started](https://developers.cloudflare.com/workers/testing/miniflare/get-started/index.md) — installation, scripts, lifecycle, and event dispatch. - [API routing](./api.md) — events and access to local resources. - [Configuration](./configuration.md) — modules, bindings, compatibility, and multiple Workers. - [Testing patterns](./patterns.md) — runtime choice, mocking, and test lifecycle. diff --git a/external/develop/devops/cloudflare/references/miniflare/api.md b/external/develop/devops/cloudflare/references/miniflare/api.md index beab829..81ad724 100644 --- a/external/develop/devops/cloudflare/references/miniflare/api.md +++ b/external/develop/devops/cloudflare/references/miniflare/api.md @@ -4,13 +4,13 @@ Use the current documentation for method signatures and examples: | Task | Documentation | |------|---------------| -| Create, reload, or dispose an instance; wait for its HTTP server | [Get started](https://developers.cloudflare.com/workers/testing/miniflare/get-started/) | -| Dispatch requests and supply request metadata | [Fetch events](https://developers.cloudflare.com/workers/testing/miniflare/core/fetch/) | -| Trigger queue and scheduled handlers programmatically | [Dispatching events](https://developers.cloudflare.com/workers/testing/miniflare/get-started/#dispatching-events) | -| Configure queue producers and consumers | [Queues](https://developers.cloudflare.com/workers/testing/miniflare/core/queues/) | -| Trigger scheduled events over HTTP or the API | [Scheduled events](https://developers.cloudflare.com/workers/testing/miniflare/core/scheduled/) | -| Access bindings from tests | [Interacting with bindings](https://developers.cloudflare.com/workers/testing/miniflare/writing-tests/#interacting-with-bindings) | -| Access local storage | [KV](https://developers.cloudflare.com/workers/testing/miniflare/storage/kv/), [R2](https://developers.cloudflare.com/workers/testing/miniflare/storage/r2/), [D1](https://developers.cloudflare.com/workers/testing/miniflare/storage/d1/), [Durable Objects](https://developers.cloudflare.com/workers/testing/miniflare/storage/durable-objects/), [Cache](https://developers.cloudflare.com/workers/testing/miniflare/storage/cache/) | -| Handle a WebSocket upgrade in a test | [WebSockets](https://developers.cloudflare.com/workers/testing/miniflare/core/web-sockets/) | +| Create, reload, or dispose an instance; wait for its HTTP server | [Get started](https://developers.cloudflare.com/workers/testing/miniflare/get-started/index.md) | +| Dispatch requests and supply request metadata | [Fetch events](https://developers.cloudflare.com/workers/testing/miniflare/core/fetch/index.md) | +| Trigger queue and scheduled handlers programmatically | [Dispatching events](https://developers.cloudflare.com/workers/testing/miniflare/get-started/index.md#dispatching-events) | +| Configure queue producers and consumers | [Queues](https://developers.cloudflare.com/workers/testing/miniflare/core/queues/index.md) | +| Trigger scheduled events over HTTP or the API | [Scheduled events](https://developers.cloudflare.com/workers/testing/miniflare/core/scheduled/index.md) | +| Access bindings from tests | [Interacting with bindings](https://developers.cloudflare.com/workers/testing/miniflare/writing-tests/index.md#interacting-with-bindings) | +| Access local storage | [KV](https://developers.cloudflare.com/workers/testing/miniflare/storage/kv/index.md), [R2](https://developers.cloudflare.com/workers/testing/miniflare/storage/r2/index.md), [D1](https://developers.cloudflare.com/workers/testing/miniflare/storage/d1/index.md), [Durable Objects](https://developers.cloudflare.com/workers/testing/miniflare/storage/durable-objects/index.md), [Cache](https://developers.cloudflare.com/workers/testing/miniflare/storage/cache/index.md) | +| Handle a WebSocket upgrade in a test | [WebSockets](https://developers.cloudflare.com/workers/testing/miniflare/core/web-sockets/index.md) | For constructor options, read [configuration.md](./configuration.md). For runtime-specific test helpers, read [patterns.md](./patterns.md). diff --git a/external/develop/devops/cloudflare/references/miniflare/configuration.md b/external/develop/devops/cloudflare/references/miniflare/configuration.md index 363343b..f989a4c 100644 --- a/external/develop/devops/cloudflare/references/miniflare/configuration.md +++ b/external/develop/devops/cloudflare/references/miniflare/configuration.md @@ -1,16 +1,16 @@ # Miniflare Configuration -Direct Miniflare does not read Wrangler configuration. Configure its bindings explicitly and build TypeScript or bundled Workers before starting tests; see [writing tests](https://developers.cloudflare.com/workers/testing/miniflare/writing-tests/). +Direct Miniflare does not read Wrangler configuration. Configure its bindings explicitly and build TypeScript or bundled Workers before starting tests; see [writing tests](https://developers.cloudflare.com/workers/testing/miniflare/writing-tests/index.md). -Match the Worker's intended compatibility date and flags when testing its behavior. Consult [compatibility dates](https://developers.cloudflare.com/workers/testing/miniflare/core/compatibility/) rather than substituting a fixed date from a sample. +Match the Worker's intended compatibility date and flags when testing its behavior. Consult [compatibility dates](https://developers.cloudflare.com/workers/testing/miniflare/core/compatibility/index.md) rather than substituting a fixed date from a sample. | Configure | Documentation | |-----------|---------------| -| Script source, HTTP server, request metadata, or reloading | [Get started](https://developers.cloudflare.com/workers/testing/miniflare/get-started/) | -| Module format and resolution rules | [Modules](https://developers.cloudflare.com/workers/testing/miniflare/core/modules/) | -| Values and file-backed bindings | [Variables and secrets](https://developers.cloudflare.com/workers/testing/miniflare/core/variables-secrets/) | -| Service bindings, shared storage, and several Workers | [Multiple Workers](https://developers.cloudflare.com/workers/testing/miniflare/core/multiple-workers/) | -| Storage bindings and documented persistence options | [KV](https://developers.cloudflare.com/workers/testing/miniflare/storage/kv/), [R2](https://developers.cloudflare.com/workers/testing/miniflare/storage/r2/), [D1](https://developers.cloudflare.com/workers/testing/miniflare/storage/d1/), [Durable Objects](https://developers.cloudflare.com/workers/testing/miniflare/storage/durable-objects/), [Cache](https://developers.cloudflare.com/workers/testing/miniflare/storage/cache/) | -| Queue producers and consumers | [Queues](https://developers.cloudflare.com/workers/testing/miniflare/core/queues/) | +| Script source, HTTP server, request metadata, or reloading | [Get started](https://developers.cloudflare.com/workers/testing/miniflare/get-started/index.md) | +| Module format and resolution rules | [Modules](https://developers.cloudflare.com/workers/testing/miniflare/core/modules/index.md) | +| Values and file-backed bindings | [Variables and secrets](https://developers.cloudflare.com/workers/testing/miniflare/core/variables-secrets/index.md) | +| Service bindings, shared storage, and several Workers | [Multiple Workers](https://developers.cloudflare.com/workers/testing/miniflare/core/multiple-workers/index.md) | +| Storage bindings and documented persistence options | [KV](https://developers.cloudflare.com/workers/testing/miniflare/storage/kv/index.md), [R2](https://developers.cloudflare.com/workers/testing/miniflare/storage/r2/index.md), [D1](https://developers.cloudflare.com/workers/testing/miniflare/storage/d1/index.md), [Durable Objects](https://developers.cloudflare.com/workers/testing/miniflare/storage/durable-objects/index.md), [Cache](https://developers.cloudflare.com/workers/testing/miniflare/storage/cache/index.md) | +| Queue producers and consumers | [Queues](https://developers.cloudflare.com/workers/testing/miniflare/core/queues/index.md) | -If the task is to run tests from the project's build and Wrangler configuration, consider the [integration test harness](https://developers.cloudflare.com/workers/testing/test-harness/) or [Workers Vitest setup](https://developers.cloudflare.com/workers/testing/vitest-integration/write-your-first-test/). +If the task is to run tests from the project's build and Wrangler configuration, consider the [integration test harness](https://developers.cloudflare.com/workers/testing/test-harness/index.md) or [Workers Vitest setup](https://developers.cloudflare.com/workers/testing/vitest-integration/write-your-first-test/index.md). diff --git a/external/develop/devops/cloudflare/references/miniflare/gotchas.md b/external/develop/devops/cloudflare/references/miniflare/gotchas.md index 0cef1b3..8503397 100644 --- a/external/develop/devops/cloudflare/references/miniflare/gotchas.md +++ b/external/develop/devops/cloudflare/references/miniflare/gotchas.md @@ -2,14 +2,14 @@ | Symptom or task | Check | |-----------------|-------| -| TypeScript, bundled code, or imports fail to load | [Custom builds](https://developers.cloudflare.com/workers/testing/miniflare/writing-tests/#custom-builds) and [module rules](https://developers.cloudflare.com/workers/testing/miniflare/core/modules/#module-rules) | -| Bindings from Wrangler configuration are missing | [Interacting with bindings](https://developers.cloudflare.com/workers/testing/miniflare/writing-tests/#interacting-with-bindings) — direct Miniflare needs explicit configuration | -| Tests disagree with Worker runtime behavior | [Test runtime differences](https://developers.cloudflare.com/workers/testing/miniflare/writing-tests/) and [compatibility dates](https://developers.cloudflare.com/workers/testing/miniflare/core/compatibility/) | -| Instances keep running, ports conflict, or request metadata is unexpected | [Instance lifecycle and HTTP server](https://developers.cloudflare.com/workers/testing/miniflare/get-started/) — dispatching a request without HTTP does not mean the instance has no HTTP server | +| TypeScript, bundled code, or imports fail to load | [Custom builds](https://developers.cloudflare.com/workers/testing/miniflare/writing-tests/index.md#custom-builds) and [module rules](https://developers.cloudflare.com/workers/testing/miniflare/core/modules/index.md#module-rules) | +| Bindings from Wrangler configuration are missing | [Interacting with bindings](https://developers.cloudflare.com/workers/testing/miniflare/writing-tests/index.md#interacting-with-bindings) — direct Miniflare needs explicit configuration | +| Tests disagree with Worker runtime behavior | [Test runtime differences](https://developers.cloudflare.com/workers/testing/miniflare/writing-tests/index.md) and [compatibility dates](https://developers.cloudflare.com/workers/testing/miniflare/core/compatibility/index.md) | +| Instances keep running, ports conflict, or request metadata is unexpected | [Instance lifecycle and HTTP server](https://developers.cloudflare.com/workers/testing/miniflare/get-started/index.md) — dispatching a request without HTTP does not mean the instance has no HTTP server | | Storage disappears or leaks across tests | Check the relevant [storage configuration](./configuration.md) and the chosen test tool's persistence settings | -| Breakpoints are needed with direct Miniflare | [Attaching a debugger](https://developers.cloudflare.com/workers/testing/miniflare/developing/debugger/) | -| Upgrade a Miniflare 2 application | [Migrate from version 2](https://developers.cloudflare.com/workers/testing/miniflare/migrations/from-v2/) | -| Upgrade an existing Workers Vitest package | [Migrate to Vitest plugin](https://developers.cloudflare.com/workers/testing/vitest-integration/migration-guides/migrate-to-vitest-plugin/) | -| Replace unstable_dev tests | [Migration guide](https://developers.cloudflare.com/workers/testing/vitest-integration/migration-guides/migrate-from-unstable-dev/) and [integration test harness](https://developers.cloudflare.com/workers/testing/test-harness/) | +| Breakpoints are needed with direct Miniflare | [Attaching a debugger](https://developers.cloudflare.com/workers/testing/miniflare/developing/debugger/index.md) | +| Upgrade a Miniflare 2 application | [Migrate from version 2](https://developers.cloudflare.com/workers/testing/miniflare/migrations/from-v2/index.md) | +| Upgrade an existing Workers Vitest package | [Migrate to Vitest plugin](https://developers.cloudflare.com/workers/testing/vitest-integration/migration-guides/migrate-to-vitest-plugin/index.md) | +| Replace unstable_dev tests | [Migration guide](https://developers.cloudflare.com/workers/testing/vitest-integration/migration-guides/migrate-from-unstable-dev/index.md) and [integration test harness](https://developers.cloudflare.com/workers/testing/test-harness/index.md) | For a migration, choose the target using [the testing-tool decision](./README.md#choose-the-testing-tool) before translating old options. A historical migration page describes that version transition; use current setup documentation for new test suites. diff --git a/external/develop/devops/cloudflare/references/miniflare/patterns.md b/external/develop/devops/cloudflare/references/miniflare/patterns.md index 3c1e895..9e9396d 100644 --- a/external/develop/devops/cloudflare/references/miniflare/patterns.md +++ b/external/develop/devops/cloudflare/references/miniflare/patterns.md @@ -1,19 +1,19 @@ # Miniflare Testing Patterns -Choose the test runtime before adapting an example. With direct Miniflare, the Worker runs in workerd while the test runner runs in Node.js; importing Worker functions into Node.js can change runtime-dependent behavior. See [writing tests](https://developers.cloudflare.com/workers/testing/miniflare/writing-tests/). +Choose the test runtime before adapting an example. With direct Miniflare, the Worker runs in workerd while the test runner runs in Node.js; importing Worker functions into Node.js can change runtime-dependent behavior. See [writing tests](https://developers.cloudflare.com/workers/testing/miniflare/writing-tests/index.md). | Task | Documentation | |------|---------------| -| Write unit tests in the Workers runtime | [Workers Vitest setup](https://developers.cloudflare.com/workers/testing/vitest-integration/write-your-first-test/) | -| Use event, Durable Object, or other runtime test helpers | [Vitest test APIs](https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/) | -| Test built Workers from an external runner | [Integration test harness](https://developers.cloudflare.com/workers/testing/test-harness/) | -| Build a custom runner with direct simulator control | [Miniflare writing tests](https://developers.cloudflare.com/workers/testing/miniflare/writing-tests/) | -| Access emulated bindings from Node.js | [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy) | -| Mock outbound requests in Workers Vitest tests | [Mock outbound requests](https://developers.cloudflare.com/workers/testing/vitest-integration/mock-outbound-requests/) | -| Understand Vitest runtime isolation and concurrency | [Isolation and concurrency](https://developers.cloudflare.com/workers/testing/vitest-integration/isolation-and-concurrency/) | -| Simulate inter-Worker calls and substitute services | [Multiple Workers](https://developers.cloudflare.com/workers/testing/miniflare/core/multiple-workers/) | +| Write unit tests in the Workers runtime | [Workers Vitest setup](https://developers.cloudflare.com/workers/testing/vitest-integration/write-your-first-test/index.md) | +| Use event, Durable Object, or other runtime test helpers | [Vitest test APIs](https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/index.md) | +| Test built Workers from an external runner | [Integration test harness](https://developers.cloudflare.com/workers/testing/test-harness/index.md) | +| Build a custom runner with direct simulator control | [Miniflare writing tests](https://developers.cloudflare.com/workers/testing/miniflare/writing-tests/index.md) | +| Access emulated bindings from Node.js | [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/index.md#getplatformproxy) | +| Mock outbound requests in Workers Vitest tests | [Mock outbound requests](https://developers.cloudflare.com/workers/testing/vitest-integration/mock-outbound-requests/index.md) | +| Understand Vitest runtime isolation and concurrency | [Isolation and concurrency](https://developers.cloudflare.com/workers/testing/vitest-integration/isolation-and-concurrency/index.md) | +| Simulate inter-Worker calls and substitute services | [Multiple Workers](https://developers.cloudflare.com/workers/testing/miniflare/core/multiple-workers/index.md) | | Test WebSockets or access local storage | [API routing](./api.md) | `getPlatformProxy` is for Node.js callers. The Workers Vitest runtime modules require tests running in the Workers runtime; they are not a substitute for calling `getPlatformProxy` in a Node.js test. -For direct Miniflare, clean up instances after tests using the documented [lifecycle](https://developers.cloudflare.com/workers/testing/miniflare/get-started/#watching-reloading-and-disposing). Choose persistence deliberately so tests do not inherit unintended state. +For direct Miniflare, clean up instances after tests using the documented [lifecycle](https://developers.cloudflare.com/workers/testing/miniflare/get-started/index.md#watching-reloading-and-disposing). Choose persistence deliberately so tests do not inherit unintended state. diff --git a/external/develop/devops/cloudflare/references/network-interconnect/README.md b/external/develop/devops/cloudflare/references/network-interconnect/README.md deleted file mode 100644 index 0ccf014..0000000 --- a/external/develop/devops/cloudflare/references/network-interconnect/README.md +++ /dev/null @@ -1,99 +0,0 @@ -# Cloudflare Network Interconnect (CNI) - -Private, high-performance connectivity to Cloudflare's network. **Enterprise-only**. - -## Connection Types - -**Direct**: Physical fiber in shared datacenter. 10/100 Gbps. You order cross-connect. - -**Partner**: Virtual via Console Connect, Equinix, Megaport, etc. Managed via partner SDN. - -**Cloud**: AWS Direct Connect or GCP Cloud Interconnect. Magic WAN only. - -## Dataplane Versions - -**v1 (Classic)**: GRE tunnel support, VLAN/BFD/LACP, asymmetric MTU (1500↓/1476↑), peering support. - -**v2 (Beta)**: No GRE, 1500 MTU both ways, no VLAN/BFD/LACP yet, ECMP instead. - -## Use Cases - -- **Magic Transit DSR**: DDoS protection, egress via ISP (v1/v2) -- **Magic Transit + Egress**: DDoS + egress via CF (v1/v2) -- **Magic WAN + Zero Trust**: Private backbone (v1 needs GRE, v2 native) -- **Peering**: Public routes at PoP (v1 only) -- **App Security**: WAF/Cache/LB (v1/v2 over Magic Transit) - -## Prerequisites - -- Enterprise plan -- IPv4 /24+ or IPv6 /48+ prefixes -- BGP ASN for v1 -- See [locations PDF](https://developers.cloudflare.com/network-interconnect/static/cni-locations-05-may-2026.pdf) - -## Specs - -- /31 point-to-point subnets -- 10km max optical distance -- 10G: 10GBASE-LR single-mode -- 100G: 100GBASE-LR4 single-mode -- **No SLA** (free service) -- Backup Internet required - -## Throughput - -| Direction | 10G | 100G | -|-----------|-----|------| -| CF → Customer | 10 Gbps | 100 Gbps | -| Customer → CF (peering) | 10 Gbps | 100 Gbps | -| Customer → CF (Magic) | 1 Gbps/tunnel or CNI | 1 Gbps/tunnel or CNI | - -## Timeline - -2-4 weeks typical. Steps: request → config review → order connection → configure → test → enable health checks → activate → monitor. - -## In This Reference -- [configuration.md](./configuration.md) - BGP, routing, setup -- [api.md](./api.md) - API endpoints, SDKs -- [patterns.md](./patterns.md) - HA, hybrid cloud, failover -- [gotchas.md](./gotchas.md) - Troubleshooting, limits - -## Reading Order by Task - -| Task | Files to Load | -|------|---------------| -| Initial setup | README → configuration.md → api.md | -| Create interconnect via API | api.md → gotchas.md | -| Design HA architecture | patterns.md → README | -| Troubleshoot connection | gotchas.md → configuration.md | -| Cloud integration (AWS/GCP) | configuration.md → patterns.md | -| Monitor + alerts | configuration.md | - -## Automation Boundary - -**API-Automatable:** -- List/create/delete interconnects (Direct, Partner) -- List available slots -- Get interconnect status -- Download LOA PDF -- Create/update CNI objects (BGP config) -- Query settings - -**Requires Account Team:** -- Initial request approval -- AWS Direct Connect setup (send LOA+VLAN to CF) -- GCP Cloud Interconnect final activation -- Partner interconnect acceptance (Equinix, Megaport) -- VLAN assignment (v1) -- Configuration document generation (v1) -- Escalations + troubleshooting support - -**Cannot Be Automated:** -- Physical cross-connect installation (Direct) -- Partner portal operations (virtual circuit ordering) -- AWS/GCP portal operations -- Maintenance window coordination - -## See Also -- [tunnel](../tunnel/) - Alternative for private network connectivity -- [spectrum](../spectrum/) - Layer 4 proxy for TCP/UDP traffic diff --git a/external/develop/devops/cloudflare/references/network-interconnect/api.md b/external/develop/devops/cloudflare/references/network-interconnect/api.md deleted file mode 100644 index 85e5e12..0000000 --- a/external/develop/devops/cloudflare/references/network-interconnect/api.md +++ /dev/null @@ -1,199 +0,0 @@ -# CNI API Reference - -See [README.md](README.md) for overview. - -## Base - -``` -https://api.cloudflare.com/client/v4 -Auth: Authorization: Bearer -``` - -## SDK Namespaces - -**Primary (recommended):** -```typescript -client.networkInterconnects.interconnects.* -client.networkInterconnects.cnis.* -client.networkInterconnects.slots.* -``` - -**Alternate (deprecated):** -```typescript -client.magicTransit.cfInterconnects.* -``` - -Use `networkInterconnects` namespace for all new code. - -## Interconnects - -```http -GET /accounts/{account_id}/cni/interconnects # Query: page, per_page -POST /accounts/{account_id}/cni/interconnects # Query: validate_only=true (optional) -GET /accounts/{account_id}/cni/interconnects/{icon} -GET /accounts/{account_id}/cni/interconnects/{icon}/status -GET /accounts/{account_id}/cni/interconnects/{icon}/loa # Returns PDF -DELETE /accounts/{account_id}/cni/interconnects/{icon} -``` - -**Create Body:** `account`, `slot_id`, `type`, `facility`, `speed`, `name`, `description` -**Status Values:** `active` | `healthy` | `unhealthy` | `pending` | `down` - -**Response Example:** -```json -{"result": [{"id": "icon_abc", "name": "prod", "type": "direct", "facility": "EWR1", "speed": "10G", "status": "active"}]} -``` - -## CNI Objects (BGP config) - -```http -GET /accounts/{account_id}/cni/cnis -POST /accounts/{account_id}/cni/cnis -GET /accounts/{account_id}/cni/cnis/{cni} -PUT /accounts/{account_id}/cni/cnis/{cni} -DELETE /accounts/{account_id}/cni/cnis/{cni} -``` - -Body: `account`, `cust_ip`, `cf_ip`, `bgp_asn`, `bgp_password`, `vlan` - -## Slots - -```http -GET /accounts/{account_id}/cni/slots -GET /accounts/{account_id}/cni/slots/{slot} -``` - -Query: `facility`, `occupied`, `speed` - -## Health Checks - -Configure via Magic Transit/WAN tunnel endpoints (CNI v2). - -```typescript -await client.magicTransit.tunnels.update(accountId, tunnelId, { - health_check: { enabled: true, target: '192.0.2.1', rate: 'high', type: 'request' }, -}); -``` - -Rates: `high` | `medium` | `low`. Types: `request` | `reply`. See [Magic Transit docs](https://developers.cloudflare.com/magic-transit/how-to/configure-tunnel-endpoints/#add-tunnels). - -## Settings - -```http -GET /accounts/{account_id}/cni/settings -PUT /accounts/{account_id}/cni/settings -``` - -Body: `default_asn` - -## TypeScript SDK - -```typescript -import Cloudflare from 'cloudflare'; - -const client = new Cloudflare({ apiToken: process.env.CF_TOKEN }); - -// List -await client.networkInterconnects.interconnects.list({ account_id: id }); - -// Create with validation -await client.networkInterconnects.interconnects.create({ - account_id: id, - account: id, - slot_id: 'slot_abc', - type: 'direct', - facility: 'EWR1', - speed: '10G', - name: 'prod-interconnect', -}, { - query: { validate_only: true }, // Dry-run validation -}); - -// Create without validation -await client.networkInterconnects.interconnects.create({ - account_id: id, - account: id, - slot_id: 'slot_abc', - type: 'direct', - facility: 'EWR1', - speed: '10G', - name: 'prod-interconnect', -}); - -// Status -await client.networkInterconnects.interconnects.get(accountId, iconId); - -// LOA (use fetch) -const res = await fetch(`https://api.cloudflare.com/client/v4/accounts/${id}/cni/interconnects/${iconId}/loa`, { - headers: { Authorization: `Bearer ${token}` }, -}); -await fs.writeFile('loa.pdf', Buffer.from(await res.arrayBuffer())); - -// CNI object -await client.networkInterconnects.cnis.create({ - account_id: id, - account: id, - cust_ip: '192.0.2.1/31', - cf_ip: '192.0.2.0/31', - bgp_asn: 65000, - vlan: 100, -}); - -// Slots (filter by facility and speed) -await client.networkInterconnects.slots.list({ - account_id: id, - occupied: false, - facility: 'EWR1', - speed: '10G', -}); -``` - -## Python SDK - -```python -from cloudflare import Cloudflare - -client = Cloudflare(api_token=os.environ["CF_TOKEN"]) - -# List, create, status (same pattern as TypeScript) -client.network_interconnects.interconnects.list(account_id=id) -client.network_interconnects.interconnects.create(account_id=id, account=id, slot_id="slot_abc", type="direct", facility="EWR1", speed="10G") -client.network_interconnects.interconnects.get(account_id=id, icon=icon_id) - -# CNI objects and slots -client.network_interconnects.cnis.create(account_id=id, cust_ip="192.0.2.1/31", cf_ip="192.0.2.0/31", bgp_asn=65000) -client.network_interconnects.slots.list(account_id=id, occupied=False) -``` - -## cURL - -```bash -# List interconnects -curl "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/cni/interconnects" \ - -H "Authorization: Bearer ${CF_TOKEN}" - -# Create interconnect -curl -X POST "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/cni/interconnects?validate_only=true" \ - -H "Authorization: Bearer ${CF_TOKEN}" -H "Content-Type: application/json" \ - -d '{"account": "id", "slot_id": "slot_abc", "type": "direct", "facility": "EWR1", "speed": "10G"}' - -# LOA PDF -curl "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/cni/interconnects/${ICON_ID}/loa" \ - -H "Authorization: Bearer ${CF_TOKEN}" --output loa.pdf -``` - -## Not Available via API - -**Missing Capabilities:** -- BGP session state query (use Dashboard or BGP logs) -- Bandwidth utilization metrics (use external monitoring) -- Traffic statistics per interconnect -- Historical uptime/downtime data -- Light level readings (contact account team) -- Maintenance window scheduling (notifications only) - -## Resources - -- [API Docs](https://developers.cloudflare.com/api/resources/network_interconnects/) -- [TypeScript SDK](https://github.com/cloudflare/cloudflare-typescript) -- [Python SDK](https://github.com/cloudflare/cloudflare-python) diff --git a/external/develop/devops/cloudflare/references/network-interconnect/configuration.md b/external/develop/devops/cloudflare/references/network-interconnect/configuration.md deleted file mode 100644 index 0f1005c..0000000 --- a/external/develop/devops/cloudflare/references/network-interconnect/configuration.md +++ /dev/null @@ -1,114 +0,0 @@ -# CNI Configuration - -See [README.md](README.md) for overview. - -## Workflow (2-4 weeks) - -1. **Submit request** (Week 1): Contact account team, provide type/location/use case -2. **Review config** (Week 1-2, v1 only): Approve IP/VLAN/spec doc -3. **Order connection** (Week 2-3): - - **Direct**: Get LOA, order cross-connect from facility - - **Partner**: Order virtual circuit in partner portal - - **Cloud**: Order Direct Connect/Cloud Interconnect, send LOA+VLAN to CF -4. **Configure** (Week 3): Both sides configure per doc -5. **Test** (Week 3-4): Ping, verify BGP, check routes -6. **Health checks** (Week 4): Configure [Magic Transit](https://developers.cloudflare.com/magic-transit/how-to/configure-tunnel-endpoints/#add-tunnels) or [Magic WAN](https://developers.cloudflare.com/magic-wan/configuration/manually/how-to/configure-tunnel-endpoints/#add-tunnels) health checks -7. **Activate** (Week 4): Route traffic, verify flow -8. **Monitor**: Enable [maintenance notifications](https://developers.cloudflare.com/network-interconnect/monitoring-and-alerts/#enable-cloudflare-status-maintenance-notification) - -## BGP Configuration - -**v1 Requirements:** -- BGP ASN (provide during setup) -- /31 subnet for peering -- Optional: BGP password - -**v2:** Simplified, less BGP config needed. - -**BGP over CNI (Dec 2024):** Magic WAN/Transit can now peer BGP directly over CNI v2 (no GRE tunnel required). - -**Example v1 BGP:** -``` -Router ID: 192.0.2.1 -Peer IP: 192.0.2.0 -Remote ASN: 13335 -Local ASN: 65000 -Password: [optional] -VLAN: 100 -``` - -## Cloud Interconnect Setup - -### AWS Direct Connect (Beta) - -**Requirements:** Magic WAN, AWS Dedicated Direct Connect 1/10 Gbps. - -**Process:** -1. Contact CF account team -2. Choose location -3. Order in AWS portal -4. AWS provides LOA + VLAN ID -5. Send to CF account team -6. Wait ~4 weeks - -**Post-setup:** Add [static routes](https://developers.cloudflare.com/magic-wan/configuration/manually/how-to/configure-routes/#configure-static-routes) to Magic WAN. Enable [bidirectional health checks](https://developers.cloudflare.com/magic-wan/configuration/manually/how-to/configure-tunnel-endpoints/#legacy-bidirectional-health-checks). - -### GCP Cloud Interconnect (Beta) - -**Setup via Dashboard:** -1. Interconnects → Create → Cloud Interconnect → Google -2. Provide name, MTU (match GCP VLAN attachment), speed (50M-50G granular options available for partner interconnects) -3. Enter VLAN attachment pairing key -4. Confirm order - -**Routing to GCP:** Add [static routes](https://developers.cloudflare.com/magic-wan/configuration/manually/how-to/configure-routes/#configure-static-routes). BGP routes from GCP Cloud Router **ignored**. - -**Routing to CF:** Configure [custom learned routes](https://cloud.google.com/network-connectivity/docs/router/how-to/configure-custom-learned-routes) in Cloud Router. Request prefixes from CF account team. - -## Monitoring - -**Dashboard Status:** - -| Status | Meaning | -|--------|---------| -| **Healthy** | Link operational, traffic flowing, health checks passing | -| **Active** | Link up, sufficient light, Ethernet negotiated | -| **Unhealthy** | Link down, no/low light (<-20 dBm), can't negotiate | -| **Pending** | Cross-connect incomplete, device unresponsive, RX/TX swapped | -| **Down** | Physical link down, no connectivity | - -**Alerts:** - -**CNI Connection Maintenance** (Magic Networking only): -``` -Dashboard → Notifications → Add -Product: Cloudflare Network Interconnect -Type: Connection Maintenance Alert -``` -Warnings up to 2 weeks advance. 6hr delay for new additions. - -**Cloudflare Status Maintenance** (entire PoP): -``` -Dashboard → Notifications → Add -Product: Cloudflare Status -Filter PoPs: gru,fra,lhr -``` - -**Find PoP code:** -``` -Dashboard → Magic Transit/WAN → Configuration → Interconnects -Select CNI → Note Data Center (e.g., "gru-b") -Use first 3 letters: "gru" -``` - -## Best Practices - -**Critical config-specific practices:** -- /31 subnets required for BGP -- BGP passwords recommended -- BFD for fast failover (v1 only) -- Test ping connectivity before BGP -- Enable maintenance notifications immediately after activation -- Monitor status programmatically via API - -For design patterns, HA architecture, and security best practices, see [patterns.md](./patterns.md). diff --git a/external/develop/devops/cloudflare/references/network-interconnect/gotchas.md b/external/develop/devops/cloudflare/references/network-interconnect/gotchas.md deleted file mode 100644 index 13a639e..0000000 --- a/external/develop/devops/cloudflare/references/network-interconnect/gotchas.md +++ /dev/null @@ -1,165 +0,0 @@ -# CNI Gotchas & Troubleshooting - -## Common Errors - -### "Status: Pending" - -**Cause:** Cross-connect not installed, RX/TX fibers reversed, wrong fiber type, or low light levels -**Solution:** -1. Verify cross-connect installed -2. Check fiber at patch panel -3. Swap RX/TX fibers -4. Check light with optical power meter (target > -20 dBm) -5. Contact account team - -### "Status: Unhealthy" - -**Cause:** Physical issue, low light (<-20 dBm), optic mismatch, or dirty connectors -**Solution:** -1. Check physical connections -2. Clean fiber connectors -3. Verify optic types (10GBASE-LR/100GBASE-LR4) -4. Test with known-good optics -5. Check patch panel -6. Contact account team - -### "BGP Session Down" - -**Cause:** Wrong IP addressing, wrong ASN, password mismatch, or firewall blocking TCP/179 -**Solution:** -1. Verify IPs match CNI object -2. Confirm ASN correct -3. Check BGP password -4. Verify no firewall on TCP/179 -5. Check BGP logs -6. Review BGP timers - -### "Low Throughput" - -**Cause:** MTU mismatch, fragmentation, single GRE tunnel (v1), or routing inefficiency -**Solution:** -1. Check MTU (1500↓/1476↑ for v1, 1500 both for v2) -2. Test various packet sizes -3. Add more GRE tunnels (v1) -4. Consider upgrading to v2 -5. Review routing tables -6. Use LACP for bundling (v1) - -## API Errors - -### 400 Bad Request: "slot_id already occupied" - -**Cause:** Another interconnect already uses this slot -**Solution:** Use `occupied=false` filter when listing slots: -```typescript -await client.networkInterconnects.slots.list({ - account_id: id, - occupied: false, - facility: 'EWR1', -}); -``` - -### 400 Bad Request: "invalid facility code" - -**Cause:** Typo or unsupported facility -**Solution:** Check [locations PDF](https://developers.cloudflare.com/network-interconnect/static/cni-locations-05-may-2026.pdf) for valid codes - -### 403 Forbidden: "Enterprise plan required" - -**Cause:** Account not enterprise-level -**Solution:** Contact account team to upgrade - -### 422 Unprocessable: "validate_only request failed" - -**Cause:** Dry-run validation found issues (wrong slot, invalid config) -**Solution:** Review error message details, fix config before real creation - -### Rate Limiting - -**Limit:** 1200 requests/5min per token -**Solution:** Implement exponential backoff, cache slot listings - -## Cloud-Specific Issues - -### AWS Direct Connect: "VLAN not matching" - -**Cause:** VLAN ID from AWS LOA doesn't match CNI config -**Solution:** -1. Get VLAN from AWS Console after ordering -2. Send exact VLAN to CF account team -3. Verify match in CNI object config - -### AWS: "Connection stuck in Pending" - -**Cause:** LOA not provided to CF or AWS connection not accepted -**Solution:** -1. Verify AWS connection status is "Available" -2. Confirm LOA sent to CF account team -3. Wait for CF team acceptance (can take days) - -### GCP: "BGP routes not propagating" - -**Cause:** BGP routes from GCP Cloud Router **ignored by design** -**Solution:** Use [static routes](https://developers.cloudflare.com/magic-wan/configuration/manually/how-to/configure-routes/#configure-static-routes) in Magic WAN instead - -### GCP: "Cannot query VLAN attachment status via API" - -**Cause:** GCP Cloud Interconnect Dashboard-only (no API yet) -**Solution:** Check status in CF Dashboard or GCP Console - -## Partner Interconnect Issues - -### Equinix: "Virtual circuit not appearing" - -**Cause:** CF hasn't accepted Equinix connection request -**Solution:** -1. Verify VC created in Equinix Fabric Portal -2. Contact CF account team to accept -3. Allow 2-3 business days - -### Console Connect/Megaport: "API creation fails" - -**Cause:** Partner interconnects require partner portal + CF approval -**Solution:** Cannot fully automate. Order in partner portal, notify CF account team. - -## Anti-Patterns - -| Anti-Pattern | Why Bad | Solution | -|--------------|---------|----------| -| Single interconnect for production | No SLA, single point of failure | Use ≥2 with device diversity | -| No backup Internet | CNI fails = total outage | Always maintain alternate path | -| Polling status every second | Rate limits, wastes API calls | Poll every 30-60s max | -| Using v1 for Magic WAN v2 workloads | GRE overhead, complexity | Use v2 for simplified routing | -| Assuming BGP session = traffic flowing | BGP up ≠ routes installed | Verify routing tables + test traffic | -| Not enabling maintenance alerts | Surprise downtime during maintenance | Enable notifications immediately | -| Hardcoding VLAN in automation | VLAN assigned by CF (v1) | Get VLAN from CNI object response | -| Using Direct without colocation | Can't access cross-connect | Use Partner or Cloud interconnect | - -## What's Not Queryable via API - -**Cannot retrieve:** -- BGP session state (use Dashboard or BGP logs) -- Light levels (contact account team) -- Historical metrics (uptime, traffic) -- Bandwidth utilization per interconnect -- Maintenance window schedules (notifications only) -- Fiber path details -- Cross-connect installation status - -**Workarounds:** -- External monitoring for BGP state -- Log aggregation for historical data -- Notifications for maintenance windows - -## Limits - -| Resource/Limit | Value | Notes | -|----------------|-------|-------| -| Max optical distance | 10km | Physical limit | -| MTU (v1) | 1500↓ / 1476↑ | Asymmetric | -| MTU (v2) | 1500 both | Symmetric | -| GRE tunnel throughput | 1 Gbps | Per tunnel (v1) | -| Recovery time | Days | No formal SLA | -| Light level minimum | -20 dBm | Target threshold | -| API rate limit | 1200 req/5min | Per token | -| Health check delay | 6 hours | New maintenance alert subscriptions | diff --git a/external/develop/devops/cloudflare/references/network-interconnect/patterns.md b/external/develop/devops/cloudflare/references/network-interconnect/patterns.md deleted file mode 100644 index 80365f0..0000000 --- a/external/develop/devops/cloudflare/references/network-interconnect/patterns.md +++ /dev/null @@ -1,166 +0,0 @@ -# CNI Patterns - -See [README.md](README.md) for overview. - -## High Availability - -**Critical:** Design for resilience from day one. - -**Requirements:** -- Device-level diversity (separate hardware) -- Backup Internet connectivity (no SLA on CNI) -- Network-resilient locations preferred -- Regular failover testing - -**Architecture:** -``` -Your Network A ──10G CNI v2──> CF CCR Device 1 - │ -Your Network B ──10G CNI v2──> CF CCR Device 2 - │ - CF Global Network (AS13335) -``` - -**Capacity Planning:** -- Plan across all links -- Account for failover scenarios -- Your responsibility - -## Pattern: Magic Transit + CNI v2 - -**Use Case:** DDoS protection, private connectivity, no GRE overhead. - -```typescript -// 1. Create interconnect -const ic = await client.networkInterconnects.interconnects.create({ - account_id: id, - type: 'direct', - facility: 'EWR1', - speed: '10G', - name: 'magic-transit-primary', -}); - -// 2. Poll until active -const status = await pollUntilActive(id, ic.id); - -// 3. Configure Magic Transit tunnel via Dashboard/API -``` - -**Benefits:** 1500 MTU both ways, simplified routing. - -## Pattern: Multi-Cloud Hybrid - -**Use Case:** AWS/GCP workloads with Cloudflare. - -**AWS Direct Connect:** -```typescript -// 1. Order Direct Connect in AWS Console -// 2. Get LOA + VLAN from AWS -// 3. Send to CF account team (no API) -// 4. Configure static routes in Magic WAN - -await configureStaticRoutes(id, { - prefix: '10.0.0.0/8', - nexthop: 'aws-direct-connect', -}); -``` - -**GCP Cloud Interconnect:** -``` -1. Get VLAN attachment pairing key from GCP Console -2. Create via Dashboard: Interconnects → Create → Cloud Interconnect → Google - - Enter pairing key, name, MTU, speed -3. Configure static routes in Magic WAN (BGP routes from GCP ignored) -4. Configure custom learned routes in GCP Cloud Router -``` - -**Note:** Dashboard-only. No API/SDK support yet. - -## Pattern: Multi-Location HA - -**Use Case:** 99.99%+ uptime. - -```typescript -// Primary (NY) -const primary = await client.networkInterconnects.interconnects.create({ - account_id: id, - type: 'direct', - facility: 'EWR1', - speed: '10G', - name: 'primary-ewr1', -}); - -// Secondary (NY, different hardware) -const secondary = await client.networkInterconnects.interconnects.create({ - account_id: id, - type: 'direct', - facility: 'EWR2', - speed: '10G', - name: 'secondary-ewr2', -}); - -// Tertiary (LA, different geography) -const tertiary = await client.networkInterconnects.interconnects.create({ - account_id: id, - type: 'partner', - facility: 'LAX1', - speed: '10G', - name: 'tertiary-lax1', -}); - -// BGP local preferences: -// Primary: 200 -// Secondary: 150 -// Tertiary: 100 -// Internet: Last resort -``` - -## Pattern: Partner Interconnect (Equinix) - -**Use Case:** Quick deployment, no colocation. - -**Setup:** -1. Order virtual circuit in Equinix Fabric Portal -2. Select Cloudflare as destination -3. Choose facility -4. Send details to CF account team -5. CF accepts in portal -6. Configure BGP - -**No API automation** – partner portals managed separately. - -## Failover & Security - -**Failover Best Practices:** -- Use BGP local preferences for priority -- Configure BFD for fast detection (v1) -- Test regularly with traffic shift -- Document runbooks - -**Security:** -- BGP password authentication -- BGP route filtering -- Monitor unexpected routes -- Magic Firewall for DDoS/threats -- Minimum API token permissions -- Rotate credentials periodically - -## Decision Matrix - -| Requirement | Recommended | -|-------------|-------------| -| Collocated with CF | Direct | -| Not collocated | Partner | -| AWS/GCP workloads | Cloud | -| 1500 MTU both ways | v2 | -| VLAN tagging | v1 | -| Public peering | v1 | -| Simplest config | v2 | -| BFD fast failover | v1 | -| LACP bundling | v1 | - -## Resources - -- [Magic Transit Docs](https://developers.cloudflare.com/magic-transit/) -- [Magic WAN Docs](https://developers.cloudflare.com/magic-wan/) -- [Argo Smart Routing](https://developers.cloudflare.com/argo-smart-routing/) diff --git a/external/develop/devops/cloudflare/references/observability/README.md b/external/develop/devops/cloudflare/references/observability/README.md index 72700a0..0b749d3 100644 --- a/external/develop/devops/cloudflare/references/observability/README.md +++ b/external/develop/devops/cloudflare/references/observability/README.md @@ -6,14 +6,14 @@ Use this reference to choose a telemetry signal and find the maintained implemen | Need | Start here | | --- | --- | -| Store, search, and investigate historical Worker logs | [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/) | -| Watch a deployment or reproduce an issue live | [Real-time logs and Wrangler tail](https://developers.cloudflare.com/workers/observability/logs/real-time-logs/) | -| Understand request flows and dependency latency | [Workers Traces](https://developers.cloudflare.com/workers/observability/traces/) | -| Monitor built-in request, error, and CPU metrics | [Metrics and analytics](https://developers.cloudflare.com/workers/observability/metrics-and-analytics/) | -| Record custom events and tenant-level usage for SQL analysis | [Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/get-started/) | -| Export logs and traces to an observability provider | [OpenTelemetry export](https://developers.cloudflare.com/workers/observability/exporting-opentelemetry-data/) | -| Apply custom filtering, transformation, or delivery logic | [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/) | -| Deliver Workers Trace Events to a supported log storage destination | [Workers Logpush](https://developers.cloudflare.com/workers/observability/logs/logpush/) | +| Store, search, and investigate historical Worker logs | [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/index.md) | +| Watch a deployment or reproduce an issue live | [Real-time logs and Wrangler tail](https://developers.cloudflare.com/workers/observability/logs/real-time-logs/index.md) | +| Understand request flows and dependency latency | [Workers Traces](https://developers.cloudflare.com/workers/observability/traces/index.md) | +| Monitor built-in request, error, and CPU metrics | [Metrics and analytics](https://developers.cloudflare.com/workers/observability/metrics-and-analytics/index.md) | +| Record custom events and tenant-level usage for SQL analysis | [Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/get-started/index.md) | +| Export logs and traces to an observability provider | [OpenTelemetry export](https://developers.cloudflare.com/observability/export/opentelemetry/index.md) | +| Apply custom filtering, transformation, or delivery logic | [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/index.md) | +| Deliver Workers Trace Events to a supported log storage destination | [Workers Logpush](https://developers.cloudflare.com/workers/observability/logs/logpush/index.md) | Workers Logs supports retained historical data; live tailing is a separate debugging workflow. Choose persistence, sampling, and export destinations deliberately rather than assuming that every signal is stored or included without usage charges. @@ -24,4 +24,4 @@ Workers Logs supports retained historical data; live tailing is a separate debug - [patterns.md](patterns.md): billing, performance, errors, tenant tracking, and export decisions. - [gotchas.md](gotchas.md): missing data, sampling, timing, privacy, and cost checks. -For broader product tasks, see [Analytics Engine](../analytics-engine/README.md), [GraphQL API](../graphql-api/README.md), and [Tail Workers](../tail-workers/README.md). +For broader product tasks, see [Analytics Engine docs](https://developers.cloudflare.com/analytics/analytics-engine/index.md), [GraphQL Analytics API docs](https://developers.cloudflare.com/analytics/graphql-api/index.md), and [Tail Workers](../tail-workers/README.md). diff --git a/external/develop/devops/cloudflare/references/observability/api.md b/external/develop/devops/cloudflare/references/observability/api.md index a92f42a..6597a6e 100644 --- a/external/develop/devops/cloudflare/references/observability/api.md +++ b/external/develop/devops/cloudflare/references/observability/api.md @@ -4,16 +4,16 @@ Fetch the applicable reference for current signatures, field locations, units, a | Task | Maintained documentation | | --- | --- | -| Emit console messages and check supported methods | [Console API](https://developers.cloudflare.com/workers/runtime-apis/console/) | -| Filter, group, and aggregate stored Workers Logs | [Query Builder](https://developers.cloudflare.com/workers/observability/query-builder/) | -| Query built-in Workers metrics with GraphQL | [Querying Workers metrics](https://developers.cloudflare.com/analytics/graphql-api/tutorials/querying-workers-metrics/) | -| Define Analytics Engine fields and call `writeDataPoint()` | [Write data points](https://developers.cloudflare.com/analytics/analytics-engine/get-started/) | -| Authenticate and query Analytics Engine datasets | [SQL API](https://developers.cloudflare.com/analytics/analytics-engine/sql-api/) | -| Calculate counts, sums, and averages on sampled events | [Analytics Engine sampling](https://developers.cloudflare.com/analytics/analytics-engine/sampling/) | -| Implement a Tail consumer and inspect event properties | [Tail handler API](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/) | -| Create and manage Logpush jobs | [Logpush API configuration](https://developers.cloudflare.com/logs/logpush/logpush-job/api-configuration/) | -| Select exported Workers event fields | [Workers Trace Events dataset](https://developers.cloudflare.com/logs/logpush/logpush-job/datasets/account/workers_trace_events/) | -| Export OTLP logs and traces | [OpenTelemetry export](https://developers.cloudflare.com/workers/observability/exporting-opentelemetry-data/) | +| Emit console messages and check supported methods | [Console API](https://developers.cloudflare.com/workers/runtime-apis/console/index.md) | +| Filter, group, and aggregate stored Workers Logs | [Query Builder](https://developers.cloudflare.com/workers/observability/query-builder/index.md) | +| Query built-in Workers metrics with GraphQL | [Querying Workers metrics](https://developers.cloudflare.com/analytics/graphql-api/tutorials/querying-workers-metrics/index.md) | +| Define Analytics Engine fields and call `writeDataPoint()` | [Write data points](https://developers.cloudflare.com/analytics/analytics-engine/get-started/index.md) | +| Authenticate and query Analytics Engine datasets | [SQL API](https://developers.cloudflare.com/analytics/analytics-engine/sql-api/index.md) | +| Calculate counts, sums, and averages on sampled events | [Analytics Engine sampling](https://developers.cloudflare.com/analytics/analytics-engine/sampling/index.md) | +| Implement a Tail consumer and inspect event properties | [Tail handler API](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/index.md) | +| Create and manage Logpush jobs | [Logpush API configuration](https://developers.cloudflare.com/logs/logpush/logpush-job/api-configuration/index.md) | +| Select exported Workers event fields | [Workers Trace Events dataset](https://developers.cloudflare.com/logs/logpush/logpush-job/datasets/account/workers_trace_events/index.md) | +| Export OTLP logs and traces | [OpenTelemetry export](https://developers.cloudflare.com/observability/export/opentelemetry/index.md) | Keep dataset field meanings and units consistent between writers and queries. Follow the SQL reference linked from the SQL API for supported date bucketing and aggregate functions; do not assume another SQL dialect's syntax works here. Account for sampling in averages as well as counts and sums. diff --git a/external/develop/devops/cloudflare/references/observability/configuration.md b/external/develop/devops/cloudflare/references/observability/configuration.md index 02a854d..536bf57 100644 --- a/external/develop/devops/cloudflare/references/observability/configuration.md +++ b/external/develop/devops/cloudflare/references/observability/configuration.md @@ -4,20 +4,20 @@ Fetch the relevant guide before configuring the selected Worker and deployment e | Task | Maintained documentation | | --- | --- | -| Enable persisted logs, structured JSON logging, and sampling | [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/) | -| Enable traces and set their sampling independently of logs | [Workers Traces](https://developers.cloudflare.com/workers/observability/traces/) | -| Configure a named deployment environment | [Wrangler environments](https://developers.cloudflare.com/workers/wrangler/environments/) and the environment example in [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/) | -| Bind an Analytics Engine dataset and write its first data point | [Analytics Engine get started](https://developers.cloudflare.com/analytics/analytics-engine/get-started/) | -| Connect a producer to a Tail Worker | [Configure Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/) | -| Create a Logpush job, configure access, and enable Worker log delivery | [Workers Logpush](https://developers.cloudflare.com/workers/observability/logs/logpush/) | -| Configure OTLP destinations, authentication, and local persistence | [Exporting OpenTelemetry data](https://developers.cloudflare.com/workers/observability/exporting-opentelemetry-data/) | +| Enable persisted logs, structured JSON logging, and sampling | [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/index.md) | +| Enable traces and set their sampling independently of logs | [Workers Traces](https://developers.cloudflare.com/workers/observability/traces/index.md) | +| Configure a named deployment environment | [Wrangler environments](https://developers.cloudflare.com/workers/wrangler/environments/index.md) and the environment example in [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/index.md) | +| Bind an Analytics Engine dataset and write its first data point | [Analytics Engine get started](https://developers.cloudflare.com/analytics/analytics-engine/get-started/index.md) | +| Connect a producer to a Tail Worker | [Configure Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/index.md) | +| Create a Logpush job, configure access, and enable Worker log delivery | [Workers Logpush](https://developers.cloudflare.com/workers/observability/logs/logpush/index.md) | +| Configure OTLP destinations, authentication, and local persistence | [OpenTelemetry export](https://developers.cloudflare.com/observability/export/opentelemetry/index.md) for destinations and authentication, and [Workers OpenTelemetry export](https://developers.cloudflare.com/workers/observability/opentelemetry-export/index.md) for Wrangler destinations and `persist` | ## Setup decisions - Confirm which account, Worker, and environment will emit telemetry, then deploy that configuration and generate representative traffic. - Decide log and trace sampling separately. Increasing sampling during an investigation changes volume and cost; restore the intended operational settings afterwards. - For Tail Workers, configure the consumer relationship on the producer Worker; use the guide for deployment order and the handler contract. -- Choose whether to persist data in Cloudflare as well as exporting it. Verify destination names, supported signal types, and credentials using the export guide. +- Choose whether to persist data in Cloudflare as well as exporting it. Verify destination names, supported signal types, and credentials using the export guides above. - Use stable structured fields and redact secrets and unnecessary personal data before emission. Configure development and production collection intentionally. See [gotchas.md](gotchas.md) when configured telemetry is missing. diff --git a/external/develop/devops/cloudflare/references/observability/gotchas.md b/external/develop/devops/cloudflare/references/observability/gotchas.md index 2525e54..bc8d879 100644 --- a/external/develop/devops/cloudflare/references/observability/gotchas.md +++ b/external/develop/devops/cloudflare/references/observability/gotchas.md @@ -4,22 +4,22 @@ | Symptom | Check and authoritative guide | | --- | --- | -| Logs missing from the dashboard | Confirm the deployed Worker/environment, collection and persistence settings, recent traffic, query time range, and sampling. Follow [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/) and [Query Builder](https://developers.cloudflare.com/workers/observability/query-builder/). | -| Live logs differ from stored logs | Confirm which workflow is being inspected; live streams can sample under load. See [real-time logs](https://developers.cloudflare.com/workers/observability/logs/real-time-logs/). | -| Traces missing or incomplete | Check trace enablement and sampling separately from logs, then consult [tracing setup](https://developers.cloudflare.com/workers/observability/traces/) and [known limitations](https://developers.cloudflare.com/workers/observability/traces/known-limitations/). | -| Export destination has no data | Check signal type, destination name, credentials, endpoint compatibility, and provider status using [OpenTelemetry export](https://developers.cloudflare.com/workers/observability/exporting-opentelemetry-data/). For a Logpush job, use [Workers Logpush](https://developers.cloudflare.com/workers/observability/logs/logpush/). | -| Tail consumer receives no events | Check the producer's consumer configuration and deployment using [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/). | -| Analytics Engine totals or averages look wrong | Account for sample weights and consistent field meanings using [sampling guidance](https://developers.cloudflare.com/analytics/analytics-engine/sampling/). Check [limits](https://developers.cloudflare.com/analytics/analytics-engine/limits/) for missing writes or expired data. | -| Very short operations appear to take no time | Read [performance and timers](https://developers.cloudflare.com/workers/runtime-apis/performance/) and [trace limitations](https://developers.cloudflare.com/workers/observability/traces/known-limitations/). Tracing does not eliminate the runtime's timing restrictions. | +| Logs missing from the dashboard | Confirm the deployed Worker/environment, collection and persistence settings, recent traffic, query time range, and sampling. Follow [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/index.md) and [Query Builder](https://developers.cloudflare.com/workers/observability/query-builder/index.md). | +| Live logs differ from stored logs | Confirm which workflow is being inspected; live streams can sample under load. See [real-time logs](https://developers.cloudflare.com/workers/observability/logs/real-time-logs/index.md). | +| Traces missing or incomplete | Check trace enablement and sampling separately from logs, then consult [tracing setup](https://developers.cloudflare.com/workers/observability/traces/index.md) and [known limitations](https://developers.cloudflare.com/workers/observability/traces/known-limitations/index.md). | +| Export destination has no data | Check signal type, destination name, credentials, endpoint compatibility, and provider status using [OpenTelemetry export](https://developers.cloudflare.com/observability/export/opentelemetry/index.md) and, for a Worker, [Workers OpenTelemetry export](https://developers.cloudflare.com/workers/observability/opentelemetry-export/index.md). For a Logpush job, use [Workers Logpush](https://developers.cloudflare.com/workers/observability/logs/logpush/index.md). | +| Tail consumer receives no events | Check the producer's consumer configuration and deployment using [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/index.md). | +| Analytics Engine totals or averages look wrong | Account for sample weights and consistent field meanings using [sampling guidance](https://developers.cloudflare.com/analytics/analytics-engine/sampling/index.md). Check [limits](https://developers.cloudflare.com/analytics/analytics-engine/limits/index.md) for missing writes or expired data. | +| Very short operations appear to take no time | Read [performance and timers](https://developers.cloudflare.com/workers/runtime-apis/performance/index.md) and [trace limitations](https://developers.cloudflare.com/workers/observability/traces/known-limitations/index.md). Tracing does not eliminate the runtime's timing restrictions. | ## Limits, retention, and cost Fetch these pages when estimating cost or diagnosing truncation and missing data: -- [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/): log size, retention, sampling, and pricing. -- [Workers Traces](https://developers.cloudflare.com/workers/observability/traces/) and [known limitations](https://developers.cloudflare.com/workers/observability/traces/known-limitations/): availability, propagation, and instrumentation constraints. -- [Workers pricing](https://developers.cloudflare.com/workers/platform/pricing/): current observability and Tail Worker billing terms. -- [Analytics Engine limits](https://developers.cloudflare.com/analytics/analytics-engine/limits/) and [pricing](https://developers.cloudflare.com/analytics/analytics-engine/pricing/): field/write limits, retention, query costs, and billing availability. -- [Workers Logpush](https://developers.cloudflare.com/workers/observability/logs/logpush/): Workers-specific eligibility, permissions, and pricing. +- [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/index.md): log size, retention, sampling, and pricing. +- [Workers Traces](https://developers.cloudflare.com/workers/observability/traces/index.md) and [known limitations](https://developers.cloudflare.com/workers/observability/traces/known-limitations/index.md): availability, propagation, and instrumentation constraints. +- [Workers pricing](https://developers.cloudflare.com/workers/platform/pricing/index.md): current observability and Tail Worker billing terms. +- [Analytics Engine limits](https://developers.cloudflare.com/analytics/analytics-engine/limits/index.md) and [pricing](https://developers.cloudflare.com/analytics/analytics-engine/pricing/index.md): field/write limits, retention, query costs, and billing availability. +- [Workers Logpush](https://developers.cloudflare.com/workers/observability/logs/logpush/index.md): Workers-specific eligibility, permissions, and pricing. Sampling reduces coverage as well as volume. Do not interpret the absence of a sampled event as proof that an error did not happen. Keep required diagnostic context while avoiding credentials, full sensitive URLs, and unnecessary personal data in logs, custom dimensions, and exported records. diff --git a/external/develop/devops/cloudflare/references/observability/patterns.md b/external/develop/devops/cloudflare/references/observability/patterns.md index 3786079..354978c 100644 --- a/external/develop/devops/cloudflare/references/observability/patterns.md +++ b/external/develop/devops/cloudflare/references/observability/patterns.md @@ -4,11 +4,11 @@ Use these decisions alongside the linked implementation guides. Application even | Task | Design decision and documentation | | --- | --- | -| Usage-based billing | Define the billable event, tenant identity, time window, and accuracy requirements. Start with the maintained [Analytics Engine billing recipe](https://developers.cloudflare.com/analytics/analytics-engine/recipes/usage-based-billing-for-your-saas-product/) and [sampling guidance](https://developers.cloudflare.com/analytics/analytics-engine/sampling/); assess whether sampled estimates satisfy the billing contract. | -| Performance monitoring | Use [built-in metrics](https://developers.cloudflare.com/workers/observability/metrics-and-analytics/) for aggregate health and [traces](https://developers.cloudflare.com/workers/observability/traces/) to investigate dependency latency. Custom measurements need consistent units and aggregation semantics; check [runtime timers](https://developers.cloudflare.com/workers/runtime-apis/performance/) before measuring CPU-only work. | -| Error tracking | Emit structured context without secrets, investigate with [Query Builder](https://developers.cloudflare.com/workers/observability/query-builder/), and choose [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/) if custom alert processing is needed. Define thresholds and duplicate handling for your alert destination. | -| Multi-tenant tracking | Choose the tenant dimension and consistent field positions using [Analytics Engine get started](https://developers.cloudflare.com/analytics/analytics-engine/get-started/) and [sampling guidance](https://developers.cloudflare.com/analytics/analytics-engine/sampling/). Enforce tenant authorization in the application that exposes analytics; a dataset index is not an access-control boundary. | -| Tail Worker filtering | Use the current [Tail handler schema](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/) for outcomes, exceptions, and timing fields, with [Tail configuration](https://developers.cloudflare.com/workers/observability/logs/tail-workers/) for producer wiring. Define filtering, redaction, and downstream failure handling for the destination. | -| OpenTelemetry export | Prefer the maintained [OTLP export integration](https://developers.cloudflare.com/workers/observability/exporting-opentelemetry-data/) for supported destinations. For Honeycomb, follow [Export to Honeycomb](https://developers.cloudflare.com/workers/observability/exporting-opentelemetry-data/honeycomb/) instead of synthesizing spans from Tail events. | +| Usage-based billing | Define the billable event, tenant identity, time window, and accuracy requirements. Start with the maintained [Analytics Engine billing recipe](https://developers.cloudflare.com/analytics/analytics-engine/recipes/usage-based-billing-for-your-saas-product/index.md) and [sampling guidance](https://developers.cloudflare.com/analytics/analytics-engine/sampling/index.md); assess whether sampled estimates satisfy the billing contract. | +| Performance monitoring | Use [built-in metrics](https://developers.cloudflare.com/workers/observability/metrics-and-analytics/index.md) for aggregate health and [traces](https://developers.cloudflare.com/workers/observability/traces/index.md) to investigate dependency latency. Custom measurements need consistent units and aggregation semantics; check [runtime timers](https://developers.cloudflare.com/workers/runtime-apis/performance/index.md) before measuring CPU-only work. | +| Error tracking | Emit structured context without secrets, investigate with [Query Builder](https://developers.cloudflare.com/workers/observability/query-builder/index.md), and choose [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/index.md) if custom alert processing is needed. Define thresholds and duplicate handling for your alert destination. | +| Multi-tenant tracking | Choose the tenant dimension and consistent field positions using [Analytics Engine get started](https://developers.cloudflare.com/analytics/analytics-engine/get-started/index.md) and [sampling guidance](https://developers.cloudflare.com/analytics/analytics-engine/sampling/index.md). Enforce tenant authorization in the application that exposes analytics; a dataset index is not an access-control boundary. | +| Tail Worker filtering | Use the current [Tail handler schema](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/index.md) for outcomes, exceptions, and timing fields, with [Tail configuration](https://developers.cloudflare.com/workers/observability/logs/tail-workers/index.md) for producer wiring. Define filtering, redaction, and downstream failure handling for the destination. | +| OpenTelemetry export | Prefer the maintained [OTLP export integration](https://developers.cloudflare.com/observability/export/opentelemetry/index.md) for supported destinations. For Honeycomb, follow [Export to Honeycomb](https://developers.cloudflare.com/observability/export/opentelemetry/honeycomb/index.md) instead of synthesizing spans from Tail events. | -Use [Workers Logpush](https://developers.cloudflare.com/workers/observability/logs/logpush/) when the requirement is Workers Trace Event delivery to a supported log destination. Use a Tail Worker when custom processing is required beyond the configured export integration. +Use [Workers Logpush](https://developers.cloudflare.com/workers/observability/logs/logpush/index.md) when the requirement is Workers Trace Event delivery to a supported log destination. Use a Tail Worker when custom processing is required beyond the configured export integration. diff --git a/external/develop/devops/cloudflare/references/pages-functions/README.md b/external/develop/devops/cloudflare/references/pages-functions/README.md index 110b0a6..50bc928 100644 --- a/external/develop/devops/cloudflare/references/pages-functions/README.md +++ b/external/develop/devops/cloudflare/references/pages-functions/README.md @@ -1,12 +1,12 @@ # Cloudflare Pages Functions -Use this reference for server-side behavior in an existing Pages project. For new applications, follow the Workers recommendation in the [Pages framework guidance](https://developers.cloudflare.com/pages/framework-guides/). +Use this reference for server-side behavior in an existing Pages project. For new applications, follow the Workers recommendation in the [Pages framework guidance](https://developers.cloudflare.com/pages/framework-guides/index.md). | Task | Documentation | | --- | --- | -| Identify filesystem routes and invocation boundaries | [Routing](https://developers.cloudflare.com/pages/functions/routing/) | -| Implement request handlers | [API reference](https://developers.cloudflare.com/pages/functions/api-reference/) | -| Understand generated Worker output | [Advanced mode](https://developers.cloudflare.com/pages/functions/advanced-mode/) | +| Identify filesystem routes and invocation boundaries | [Routing](https://developers.cloudflare.com/pages/functions/routing/index.md) | +| Implement request handlers | [API reference](https://developers.cloudflare.com/pages/functions/api-reference/index.md) | +| Understand generated Worker output | [Advanced mode](https://developers.cloudflare.com/pages/functions/advanced-mode/index.md) | Inspect whether the project uses a Functions directory or framework-generated advanced mode before selecting a routing approach. Fetch current documentation for signatures, supported bindings, configuration, and examples. diff --git a/external/develop/devops/cloudflare/references/pages-functions/api.md b/external/develop/devops/cloudflare/references/pages-functions/api.md index 7c18093..2e05e99 100644 --- a/external/develop/devops/cloudflare/references/pages-functions/api.md +++ b/external/develop/devops/cloudflare/references/pages-functions/api.md @@ -4,10 +4,10 @@ Fetch the API reference before writing a handler; keep runtime types and example | Task | Documentation | | --- | --- | -| Choose method handlers and access EventContext | [API reference](https://developers.cloudflare.com/pages/functions/api-reference/) | -| Read parameters and resolve dynamic routes | [Routing](https://developers.cloudflare.com/pages/functions/routing/) | -| Compose middleware and continue a request | [Middleware](https://developers.cloudflare.com/pages/functions/middleware/) | -| Use a supported resource binding | [Bindings](https://developers.cloudflare.com/pages/functions/bindings/) | -| Handle requests through generated Worker output | [Advanced mode](https://developers.cloudflare.com/pages/functions/advanced-mode/) | +| Choose method handlers and access EventContext | [API reference](https://developers.cloudflare.com/pages/functions/api-reference/index.md) | +| Read parameters and resolve dynamic routes | [Routing](https://developers.cloudflare.com/pages/functions/routing/index.md) | +| Compose middleware and continue a request | [Middleware](https://developers.cloudflare.com/pages/functions/middleware/index.md) | +| Use a supported resource binding | [Bindings](https://developers.cloudflare.com/pages/functions/bindings/index.md) | +| Handle requests through generated Worker output | [Advanced mode](https://developers.cloudflare.com/pages/functions/advanced-mode/index.md) | Decide which handler owns the response, where shared state is established, and which paths should fall through to assets. The API reference also covers asynchronous work and asset fetching. See [configuration.md](./configuration.md) for binding setup and [patterns.md](./patterns.md) for request design. diff --git a/external/develop/devops/cloudflare/references/pages-functions/configuration.md b/external/develop/devops/cloudflare/references/pages-functions/configuration.md index 33bfbb0..ec23c49 100644 --- a/external/develop/devops/cloudflare/references/pages-functions/configuration.md +++ b/external/develop/devops/cloudflare/references/pages-functions/configuration.md @@ -4,11 +4,11 @@ Read Pages-specific configuration before reusing settings from a Worker project. | Task | Documentation | | --- | --- | -| Manage Wrangler settings and environment overrides | [Functions configuration](https://developers.cloudflare.com/pages/functions/wrangler-configuration/) | -| Configure supported bindings, variables, and secrets | [Bindings](https://developers.cloudflare.com/pages/functions/bindings/) | -| Generate and configure runtime and environment types | [TypeScript](https://developers.cloudflare.com/pages/functions/typescript/) | -| Run assets and Functions locally | [Local development](https://developers.cloudflare.com/pages/functions/local-development/) | -| Set Function invocation routes | [Routing](https://developers.cloudflare.com/pages/functions/routing/) | -| Evaluate and enable placement | [Smart Placement](https://developers.cloudflare.com/pages/functions/smart-placement/) | +| Manage Wrangler settings and environment overrides | [Functions configuration](https://developers.cloudflare.com/pages/functions/wrangler-configuration/index.md) | +| Configure supported bindings, variables, and secrets | [Bindings](https://developers.cloudflare.com/pages/functions/bindings/index.md) | +| Generate and configure runtime and environment types | [TypeScript](https://developers.cloudflare.com/pages/functions/typescript/index.md) | +| Run assets and Functions locally | [Local development](https://developers.cloudflare.com/pages/functions/local-development/index.md) | +| Set Function invocation routes | [Routing](https://developers.cloudflare.com/pages/functions/routing/index.md) | +| Evaluate and enable placement | [Smart Placement](https://developers.cloudflare.com/pages/functions/smart-placement/index.md) | Identify the target deployment environment and which configuration source controls it. Verify Pages support for each binding and the documented local-development behavior before accessing remote resources. See [Pages configuration](../pages/configuration.md) for build output, headers, and redirects. diff --git a/external/develop/devops/cloudflare/references/pages-functions/gotchas.md b/external/develop/devops/cloudflare/references/pages-functions/gotchas.md index 1e16260..e0ba806 100644 --- a/external/develop/devops/cloudflare/references/pages-functions/gotchas.md +++ b/external/develop/devops/cloudflare/references/pages-functions/gotchas.md @@ -4,14 +4,14 @@ Start with the request path, deployment environment, and generated output that a | Task | Documentation | | --- | --- | -| A Function does not run or receives unexpected parameters | [Routing](https://developers.cloudflare.com/pages/functions/routing/) | -| Middleware is skipped or static fallback fails | [Advanced mode](https://developers.cloudflare.com/pages/functions/advanced-mode/) | -| Middleware order or scope is incorrect | [Middleware](https://developers.cloudflare.com/pages/functions/middleware/) | -| Bindings or secrets differ between environments | [Bindings](https://developers.cloudflare.com/pages/functions/bindings/) | -| Runtime or environment types do not match | [TypeScript](https://developers.cloudflare.com/pages/functions/typescript/) | -| A local request behaves differently | [Local development](https://developers.cloudflare.com/pages/functions/local-development/) | -| Inspect exceptions and deployment logs | [Debugging and logging](https://developers.cloudflare.com/pages/functions/debugging-and-logging/) | -| Check runtime quotas | [Workers limits](https://developers.cloudflare.com/workers/platform/limits/) | -| Check request costs | [Functions pricing](https://developers.cloudflare.com/pages/functions/pricing/) | +| A Function does not run or receives unexpected parameters | [Routing](https://developers.cloudflare.com/pages/functions/routing/index.md) | +| Middleware is skipped or static fallback fails | [Advanced mode](https://developers.cloudflare.com/pages/functions/advanced-mode/index.md) | +| Middleware order or scope is incorrect | [Middleware](https://developers.cloudflare.com/pages/functions/middleware/index.md) | +| Bindings or secrets differ between environments | [Bindings](https://developers.cloudflare.com/pages/functions/bindings/index.md) | +| Runtime or environment types do not match | [TypeScript](https://developers.cloudflare.com/pages/functions/typescript/index.md) | +| A local request behaves differently | [Local development](https://developers.cloudflare.com/pages/functions/local-development/index.md) | +| Inspect exceptions and deployment logs | [Debugging and logging](https://developers.cloudflare.com/pages/functions/debugging-and-logging/index.md) | +| Check runtime quotas | [Workers limits](https://developers.cloudflare.com/workers/platform/limits/index.md) | +| Check request costs | [Functions pricing](https://developers.cloudflare.com/pages/functions/pricing/index.md) | Reproduce a failing path through the actual application rather than only calling a handler with a hand-built context. Check the deployed configuration and generated output before changing application code. See [Pages troubleshooting](../pages/gotchas.md) for build, asset, and framework issues. diff --git a/external/develop/devops/cloudflare/references/pages-functions/patterns.md b/external/develop/devops/cloudflare/references/pages-functions/patterns.md index b2c9257..8dc4d8f 100644 --- a/external/develop/devops/cloudflare/references/pages-functions/patterns.md +++ b/external/develop/devops/cloudflare/references/pages-functions/patterns.md @@ -4,12 +4,12 @@ Choose where behavior belongs before adapting an example. | Task | Documentation | | --- | --- | -| Share authentication, logging, and error handling | [Middleware](https://developers.cloudflare.com/pages/functions/middleware/) | -| Choose request handlers and asynchronous completion behavior | [API reference](https://developers.cloudflare.com/pages/functions/api-reference/) | -| Keep asset requests outside Function invocation where appropriate | [Routing](https://developers.cloudflare.com/pages/functions/routing/) | -| Use custom or framework-generated routing | [Advanced mode](https://developers.cloudflare.com/pages/functions/advanced-mode/) | -| Integrate storage or another service | [Bindings](https://developers.cloudflare.com/pages/functions/bindings/) | -| Exercise the assembled application locally | [Local development](https://developers.cloudflare.com/pages/functions/local-development/) | +| Share authentication, logging, and error handling | [Middleware](https://developers.cloudflare.com/pages/functions/middleware/index.md) | +| Choose request handlers and asynchronous completion behavior | [API reference](https://developers.cloudflare.com/pages/functions/api-reference/index.md) | +| Keep asset requests outside Function invocation where appropriate | [Routing](https://developers.cloudflare.com/pages/functions/routing/index.md) | +| Use custom or framework-generated routing | [Advanced mode](https://developers.cloudflare.com/pages/functions/advanced-mode/index.md) | +| Integrate storage or another service | [Bindings](https://developers.cloudflare.com/pages/functions/bindings/index.md) | +| Exercise the assembled application locally | [Local development](https://developers.cloudflare.com/pages/functions/local-development/index.md) | Define the response owner and middleware scope before adding authentication or response transformations. Test protected routes, rejected requests, and static fallbacks together. Choose consistency and concurrency requirements before using storage for session state or rate limiting; a generic read-modify-write example is not a complete policy. diff --git a/external/develop/devops/cloudflare/references/pages/README.md b/external/develop/devops/cloudflare/references/pages/README.md index 88f67eb..4bea4c7 100644 --- a/external/develop/devops/cloudflare/references/pages/README.md +++ b/external/develop/devops/cloudflare/references/pages/README.md @@ -1,14 +1,14 @@ # Cloudflare Pages -Use this reference when maintaining an existing Pages project. For new applications, start with Workers as recommended in the [Pages framework guidance](https://developers.cloudflare.com/pages/framework-guides/). Fetch current documentation before implementing. +Use this reference when maintaining an existing Pages project. For new applications, start with Workers as recommended in the [Pages framework guidance](https://developers.cloudflare.com/pages/framework-guides/index.md). Fetch current documentation before implementing. | Task | Documentation | | --- | --- | -| Configure the existing build | [Build configuration](https://developers.cloudflare.com/pages/configuration/build-configuration/) | -| Manage automatic deployments from a repository | [Git integration](https://developers.cloudflare.com/pages/configuration/git-integration/) | -| Deploy prebuilt output | [Direct Upload](https://developers.cloudflare.com/pages/get-started/direct-upload/) | -| Implement server-side requests | [Functions API reference](https://developers.cloudflare.com/pages/functions/api-reference/) | -| Plan a move to Workers | [Migrate from Pages to Workers](https://developers.cloudflare.com/workers/static-assets/migration-guides/migrate-from-pages/) | +| Configure the existing build | [Build configuration](https://developers.cloudflare.com/pages/configuration/build-configuration/index.md) | +| Manage automatic deployments from a repository | [Git integration](https://developers.cloudflare.com/pages/configuration/git-integration/index.md) | +| Deploy prebuilt output | [Direct Upload](https://developers.cloudflare.com/pages/get-started/direct-upload/index.md) | +| Implement server-side requests | [Functions API reference](https://developers.cloudflare.com/pages/functions/api-reference/index.md) | +| Plan a move to Workers | [Migrate from Pages to Workers](https://developers.cloudflare.com/workers/static-assets/migration-guides/migrate-from-pages/index.md) | ## In This Reference diff --git a/external/develop/devops/cloudflare/references/pages/api.md b/external/develop/devops/cloudflare/references/pages/api.md index 4169a40..3081ded 100644 --- a/external/develop/devops/cloudflare/references/pages/api.md +++ b/external/develop/devops/cloudflare/references/pages/api.md @@ -4,10 +4,10 @@ Use the current Pages API and the project framework guide rather than translatin | Task | Documentation | | --- | --- | -| Implement handlers and use context or asset fallback | [Functions API reference](https://developers.cloudflare.com/pages/functions/api-reference/) | -| Resolve dynamic paths and invocation routes | [Routing](https://developers.cloudflare.com/pages/functions/routing/) | -| Apply shared request logic | [Middleware](https://developers.cloudflare.com/pages/functions/middleware/) | -| Understand framework-generated Worker output | [Advanced mode](https://developers.cloudflare.com/pages/functions/advanced-mode/) | -| Find the guide for the existing framework | [Framework guides](https://developers.cloudflare.com/pages/framework-guides/) | +| Implement handlers and use context or asset fallback | [Functions API reference](https://developers.cloudflare.com/pages/functions/api-reference/index.md) | +| Resolve dynamic paths and invocation routes | [Routing](https://developers.cloudflare.com/pages/functions/routing/index.md) | +| Apply shared request logic | [Middleware](https://developers.cloudflare.com/pages/functions/middleware/index.md) | +| Understand framework-generated Worker output | [Advanced mode](https://developers.cloudflare.com/pages/functions/advanced-mode/index.md) | +| Find the guide for the existing framework | [Framework guides](https://developers.cloudflare.com/pages/framework-guides/index.md) | First identify whether routing comes from the Functions directory or generated advanced-mode output. See [Pages Functions APIs](../pages-functions/api.md) for focused handler tasks and [patterns.md](./patterns.md) for migration decisions. diff --git a/external/develop/devops/cloudflare/references/pages/configuration.md b/external/develop/devops/cloudflare/references/pages/configuration.md index 6a48dca..4092c29 100644 --- a/external/develop/devops/cloudflare/references/pages/configuration.md +++ b/external/develop/devops/cloudflare/references/pages/configuration.md @@ -4,13 +4,13 @@ Inspect the existing project configuration and build output before changing depl | Task | Documentation | | --- | --- | -| Set build commands, root/output directories, and build variables | [Build configuration](https://developers.cloudflare.com/pages/configuration/build-configuration/) | -| Manage Wrangler configuration, environments, and dashboard migration | [Functions configuration](https://developers.cloudflare.com/pages/functions/wrangler-configuration/) | -| Configure resource bindings, variables, and secrets | [Bindings](https://developers.cloudflare.com/pages/functions/bindings/) | -| Set static response headers | [Headers](https://developers.cloudflare.com/pages/configuration/headers/) | -| Configure static redirects and rewrites | [Redirects](https://developers.cloudflare.com/pages/configuration/redirects/) | -| Choose which requests invoke Functions | [Routing](https://developers.cloudflare.com/pages/functions/routing/) | -| Configure monorepo project boundaries | [Monorepos](https://developers.cloudflare.com/pages/configuration/monorepos/) | -| Run the project locally | [Local development](https://developers.cloudflare.com/pages/functions/local-development/) | +| Set build commands, root/output directories, and build variables | [Build configuration](https://developers.cloudflare.com/pages/configuration/build-configuration/index.md) | +| Manage Wrangler configuration, environments, and dashboard migration | [Functions configuration](https://developers.cloudflare.com/pages/functions/wrangler-configuration/index.md) | +| Configure resource bindings, variables, and secrets | [Bindings](https://developers.cloudflare.com/pages/functions/bindings/index.md) | +| Set static response headers | [Headers](https://developers.cloudflare.com/pages/configuration/headers/index.md) | +| Configure static redirects and rewrites | [Redirects](https://developers.cloudflare.com/pages/configuration/redirects/index.md) | +| Choose which requests invoke Functions | [Routing](https://developers.cloudflare.com/pages/functions/routing/index.md) | +| Configure monorepo project boundaries | [Monorepos](https://developers.cloudflare.com/pages/configuration/monorepos/index.md) | +| Run the project locally | [Local development](https://developers.cloudflare.com/pages/functions/local-development/index.md) | Distinguish build-time variables from runtime bindings, and check preview and production separately. Determine whether the framework owns generated routing files before editing them. See [Pages Functions configuration](../pages-functions/configuration.md) for types and placement. diff --git a/external/develop/devops/cloudflare/references/pages/gotchas.md b/external/develop/devops/cloudflare/references/pages/gotchas.md index 76250eb..bbac096 100644 --- a/external/develop/devops/cloudflare/references/pages/gotchas.md +++ b/external/develop/devops/cloudflare/references/pages/gotchas.md @@ -4,16 +4,16 @@ Identify whether the failure occurs during the build, asset serving, or Function | Task | Documentation | | --- | --- | -| Build output is missing or incorrect | [Build configuration](https://developers.cloudflare.com/pages/configuration/build-configuration/) | -| A static URL redirects or returns an unexpected 404 | [Serving Pages](https://developers.cloudflare.com/pages/configuration/serving-pages/) | -| Static response headers are not applied | [Headers](https://developers.cloudflare.com/pages/configuration/headers/) | -| A redirect rule does not match | [Redirects](https://developers.cloudflare.com/pages/configuration/redirects/) | -| Investigate a failed Function request | [Debugging and logging](https://developers.cloudflare.com/pages/functions/debugging-and-logging/) | -| Check deployment and file capacity | [Pages limits](https://developers.cloudflare.com/pages/platform/limits/) | -| Understand Function versus static request billing | [Functions pricing](https://developers.cloudflare.com/pages/functions/pricing/) | +| Build output is missing or incorrect | [Build configuration](https://developers.cloudflare.com/pages/configuration/build-configuration/index.md) | +| A static URL redirects or returns an unexpected 404 | [Serving Pages](https://developers.cloudflare.com/pages/configuration/serving-pages/index.md) | +| Static response headers are not applied | [Headers](https://developers.cloudflare.com/pages/configuration/headers/index.md) | +| A redirect rule does not match | [Redirects](https://developers.cloudflare.com/pages/configuration/redirects/index.md) | +| Investigate a failed Function request | [Debugging and logging](https://developers.cloudflare.com/pages/functions/debugging-and-logging/index.md) | +| Check deployment and file capacity | [Pages limits](https://developers.cloudflare.com/pages/platform/limits/index.md) | +| Understand Function versus static request billing | [Functions pricing](https://developers.cloudflare.com/pages/functions/pricing/index.md) | Compare the same route in local, preview, and production environments; record the build output and configuration used by each. See [Pages Functions troubleshooting](../pages-functions/gotchas.md) for handler and binding issues. ## Framework-Specific -Fetch the relevant [framework guide](https://developers.cloudflare.com/pages/framework-guides/) before changing adapters or recommending another host. For a move to Workers, follow the [migration guide](https://developers.cloudflare.com/workers/static-assets/migration-guides/migrate-from-pages/). +Fetch the relevant [framework guide](https://developers.cloudflare.com/pages/framework-guides/index.md) before changing adapters or recommending another host. For a move to Workers, follow the [migration guide](https://developers.cloudflare.com/workers/static-assets/migration-guides/migrate-from-pages/index.md). diff --git a/external/develop/devops/cloudflare/references/pages/patterns.md b/external/develop/devops/cloudflare/references/pages/patterns.md index 6613abe..810fe88 100644 --- a/external/develop/devops/cloudflare/references/pages/patterns.md +++ b/external/develop/devops/cloudflare/references/pages/patterns.md @@ -4,11 +4,11 @@ Keep project-specific choices here; fetch framework adapters, configuration, and | Task | Documentation | | --- | --- | -| Maintain a framework deployment | [Framework guides](https://developers.cloudflare.com/pages/framework-guides/) | -| Deploy from an external build pipeline | [Direct Upload](https://developers.cloudflare.com/pages/get-started/direct-upload/) | -| Manage multiple apps in one repository | [Monorepos](https://developers.cloudflare.com/pages/configuration/monorepos/) | -| Evaluate backend locality | [Smart Placement](https://developers.cloudflare.com/pages/functions/smart-placement/) | -| Plan a move to Workers | [Migrate from Pages to Workers](https://developers.cloudflare.com/workers/static-assets/migration-guides/migrate-from-pages/) | +| Maintain a framework deployment | [Framework guides](https://developers.cloudflare.com/pages/framework-guides/index.md) | +| Deploy from an external build pipeline | [Direct Upload](https://developers.cloudflare.com/pages/get-started/direct-upload/index.md) | +| Manage multiple apps in one repository | [Monorepos](https://developers.cloudflare.com/pages/configuration/monorepos/index.md) | +| Evaluate backend locality | [Smart Placement](https://developers.cloudflare.com/pages/functions/smart-placement/index.md) | +| Plan a move to Workers | [Migrate from Pages to Workers](https://developers.cloudflare.com/workers/static-assets/migration-guides/migrate-from-pages/index.md) | Check the existing build command, adapter, and output ownership together. Evaluate placement using the application’s backend dependencies and measured latency. For a migration, inventory routes, middleware, bindings, static rules, and deployment settings before following the migration guide. diff --git a/external/develop/devops/cloudflare/references/pipelines/README.md b/external/develop/devops/cloudflare/references/pipelines/README.md deleted file mode 100644 index d8dd08b..0000000 --- a/external/develop/devops/cloudflare/references/pipelines/README.md +++ /dev/null @@ -1,90 +0,0 @@ -# Cloudflare Pipelines - -Streaming ingest: receive events over HTTP/Workers/Logpush, transform with SQL, write to R2 as Iceberg tables or Parquet/JSON files. - -## Documentation - -This reference is a fast-start with verified code and gotchas. For limits, settings, full SQL syntax, and pricing, **retrieve the live docs** — use the Cloudflare MCP `docs` tool if available, otherwise `webfetch` the URL. Docs are source of truth over this file. - -| Topic | URL | -|-------|-----| -| Overview / getting started | `https://developers.cloudflare.com/pipelines/getting-started/` | -| Streams (write, manage, Logpush) | `https://developers.cloudflare.com/pipelines/streams/` | -| Sinks | `https://developers.cloudflare.com/pipelines/sinks/` | -| Pipelines & SQL transforms | `https://developers.cloudflare.com/pipelines/pipelines/` | -| SQL reference (statements, types) | `https://developers.cloudflare.com/pipelines/sql-reference/` | -| Wrangler commands | `https://developers.cloudflare.com/pipelines/reference/wrangler-commands/` | -| Terraform | `https://developers.cloudflare.com/pipelines/reference/terraform/` | -| Limits | `https://developers.cloudflare.com/pipelines/platform/limits/` | -| Pricing | `https://developers.cloudflare.com/pipelines/platform/pricing/` | -| Metrics (GraphQL) | `https://developers.cloudflare.com/pipelines/observability/metrics/` | - -## Three Components - -``` -Sources → Stream → Pipeline (SQL) → Sink → R2 - ↑ ↓ ↓ - HTTP / Workers / Transform Iceberg (Data Catalog) - Logpush (row-level) or Parquet/JSON files -``` - -| Component | Purpose | -|-----------|---------| -| **Stream** | Receives events (HTTP endpoint, Worker binding, or Logpush). Structured (schema-validated) or unstructured. | -| **Pipeline** | SQL connecting a stream to a sink. Row-level transforms only — no GROUP BY/aggregation. | -| **Sink** | Writes to R2 — Iceberg via Data Catalog, or raw Parquet/JSON. | - -**Status:** Open beta (Workers Paid for production). Pricing announced; verify billing status in docs. - -## Quick Start - -```bash -# Interactive — creates stream + sink + pipeline, optionally bucket + catalog -npx wrangler pipelines setup -``` - -Minimal Worker producer: -```typescript -interface Env { MY_STREAM: Pipeline; } - -export default { - async fetch(req: Request, env: Env, ctx: ExecutionContext): Promise { - ctx.waitUntil(env.MY_STREAM.send([{ event_id: crypto.randomUUID(), amount: 29.99 }])); - return new Response("OK"); - } -} satisfies ExportedHandler; -``` - -## Which Sink Type? - -``` -Need SQL queries / ACID / time-travel on the data? - → R2 Data Catalog (Iceberg) ✅ R2 SQL, schema evolution ❌ more setup - -Just archival / external tools (Spark, Athena)? - → R2 raw files (Parquet/JSON) ✅ simple, partitioned files ❌ no built-in SQL -``` - -## Critical Behaviors (read before building) - -These are non-obvious and prevent most failures — see [gotchas.md](gotchas.md) for detail. - -- **Everything is immutable after creation** — stream schema, pipeline SQL, sink config. To change, delete and recreate. -- **Sinks create their own table** — they cannot target an existing Iceberg table. -- **`__ingest_ts` is added automatically** (TIMESTAMP, partitioned by day). Don't define it in your schema. -- **Data isn't queryable immediately** — first flush takes **3–7 minutes** (warm-up + table creation) even with a short roll interval. -- **Schema validation is deferred** — invalid events are accepted then silently dropped. Monitor via GraphQL error metrics. -- **Binding field renamed `pipeline` → `stream`** (June 2026); old field still accepted. - -## Reading Order - -1. [configuration.md](configuration.md) — schema, streams, sinks, pipelines (CLI + REST + Terraform), bindings -2. [api.md](api.md) — `send()`, HTTP ingest, REST API, pipeline SQL, lifecycle states -3. [patterns.md](patterns.md) — fire-and-forget, validation, Logpush, observability, end-to-end -4. [gotchas.md](gotchas.md) — silent drops, immutability, REST≠CLI field names - -## See Also - -- [r2-data-catalog](../r2-data-catalog/) — Iceberg sink destination -- [r2-sql](../r2-sql/) — query the ingested data -- [r2](../r2/) · [queues](../queues/) · [workers](https://developers.cloudflare.com/workers/) diff --git a/external/develop/devops/cloudflare/references/pipelines/api.md b/external/develop/devops/cloudflare/references/pipelines/api.md deleted file mode 100644 index 98cb953..0000000 --- a/external/develop/devops/cloudflare/references/pipelines/api.md +++ /dev/null @@ -1,124 +0,0 @@ -# Pipelines API Reference - -Code templates and verified behavior. For the full SQL function set and HTTP status semantics, pull `https://developers.cloudflare.com/pipelines/sql-reference/` and the streams docs. - -## Worker Binding Interface - -```typescript -// from cloudflare:pipelines / @cloudflare/workers-types -interface Pipeline { send(records: T[]): Promise; } - -interface Env { MY_STREAM: Pipeline; } - -export default { - async fetch(req: Request, env: Env, ctx: ExecutionContext): Promise { - await env.MY_STREAM.send([{ event_id: crypto.randomUUID(), amount: 29.99 }]); - return new Response("OK"); - } -} satisfies ExportedHandler; -``` - -- `send()` takes an **array**, returns `Promise` (no confirmation payload). -- Throws on network errors — wrap in try/catch or use `ctx.waitUntil()` for fire-and-forget. -- Validation errors are **not** thrown here (deferred during processing — see [gotchas.md](gotchas.md)). -- Payload/rate limits apply — check `https://developers.cloudflare.com/pipelines/platform/limits/` before sizing batches. - -## HTTP Ingest - -``` -https://{stream-id}.ingest.cloudflare.com -``` - -Get `{stream-id}` from `npx wrangler pipelines streams list`. - -```bash -# Batch (preferred) -curl -X POST https://{stream-id}.ingest.cloudflare.com \ - -H "Content-Type: application/json" \ - -d '[{"event_id":"evt-1","amount":29.99},{"event_id":"evt-2","amount":14.99}]' - -# Single event — auto-wrapped in an array -curl -X POST https://{stream-id}.ingest.cloudflare.com \ - -H "Content-Type: application/json" -d '{"event_id":"evt-3","amount":9.99}' -``` - -If stream auth is enabled, add `-H "Authorization: Bearer $TOKEN"` (token needs **Workers Pipelines Send**). Standard HTTP status codes apply (400 invalid, 401 auth, 413 too large, 429 rate-limited, 5xx retry). - -> **JSON only** — no Avro, Protobuf, or CSV input. - -## REST Management API - -Base: `https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pipelines/v1` - -```bash -# List -curl -s "$BASE_URL/streams" -H "Authorization: Bearer $API_TOKEN" -curl -s "$BASE_URL/sinks" -H "Authorization: Bearer $API_TOKEN" -curl -s "$BASE_URL/pipelines" -H "Authorization: Bearer $API_TOKEN" - -# Get one (pipeline GET includes status + failure_reason — useful for debugging) -curl -s "$BASE_URL/pipelines/{pipeline-id}" -H "Authorization: Bearer $API_TOKEN" - -# Delete in reverse order: pipeline → sink → stream -curl -X DELETE "$BASE_URL/pipelines/{id}" -H "Authorization: Bearer $API_TOKEN" -curl -X DELETE "$BASE_URL/sinks/{id}" -H "Authorization: Bearer $API_TOKEN" -curl -X DELETE "$BASE_URL/streams/{id}" -H "Authorization: Bearer $API_TOKEN" -``` - -> `wrangler pipelines delete` defaults to "no" non-interactively — use the REST API for automated cleanup. Deleting a stream removes buffered events and dependent pipelines. - -### Pipeline Lifecycle States - -| Status | Meaning | -|--------|---------| -| `running` | Active, processing events | -| `initializing` | Starting up (minutes after creation or recovery) | -| `failed` | Stopped on error — check `failure_reason` (expired token, deleted bucket, disabled catalog) | - -> A `GET` on a sink shows `schema.fields: []` — expected. The sink inherits schema from the stream via the pipeline SQL. - -## Pipeline SQL (Transforms) - -Row-level only — no GROUP BY/aggregation. CTEs (`WITH`) and `UNNEST` are supported. Full function list: `https://developers.cloudflare.com/pipelines/sql-reference/`. - -```sql --- Passthrough / filter / enrich -INSERT INTO my_sink SELECT * FROM my_stream; -INSERT INTO my_sink SELECT * FROM my_stream WHERE amount > 10; -INSERT INTO my_sink -SELECT event_id, UPPER(category) AS category, amount * 1.1 AS amount_with_tax -FROM my_stream; - --- CTE -WITH filtered AS (SELECT event_id, amount FROM my_stream WHERE amount > 50) -INSERT INTO my_sink SELECT * FROM filtered; - --- UNNEST arrays (one per SELECT) -SELECT UNNEST(tags) AS tag FROM my_stream; -``` - -Supported categories: string, regex, hashing (`sha256`), JSON extraction, timestamp conversion, conditional (`CASE`), `CAST`, `COALESCE`, math/comparison operators. - -## Verifying End-to-End Data Flow - -```bash -# 1. Pipeline running (not initializing/failed)? -curl -s "$BASE_URL/pipelines/{id}" -H "Authorization: Bearer $API_TOKEN" - -# 2. Table created yet? (3–7 min on first flush) -curl -s "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/r2-catalog/$BUCKET/namespaces/my_ns/tables" \ - -H "Authorization: Bearer $API_TOKEN" - -# 3. Data present? (R2 SQL) -curl -s -X POST \ - "https://api.sql.cloudflarestorage.com/api/v1/accounts/$ACCOUNT_ID/r2-sql/query/$BUCKET" \ - -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \ - -d '{"query": "SELECT COUNT(*) AS total FROM my_ns.my_table"}' -``` - -> Expect **3–7 minutes** from first send to first queryable data. Subsequent flushes are much faster. - -## See Also - -- [configuration.md](configuration.md) — creating resources · [patterns.md](patterns.md) — producers, Logpush, observability -- [r2-sql/api.md](../r2-sql/api.md) — querying results diff --git a/external/develop/devops/cloudflare/references/pipelines/configuration.md b/external/develop/devops/cloudflare/references/pipelines/configuration.md deleted file mode 100644 index 464b589..0000000 --- a/external/develop/devops/cloudflare/references/pipelines/configuration.md +++ /dev/null @@ -1,155 +0,0 @@ -# Pipelines Configuration - -Templates for creating streams, sinks, and pipelines via CLI, REST, or Terraform. For the full flag/field list and allowed values, pull `https://developers.cloudflare.com/pipelines/reference/wrangler-commands/` and the streams/sinks/pipelines docs. - -## Naming Rules - -- **Streams, sinks, pipelines** use underscores: `my_stream`, `my_sink`, `my_pipeline`. -- **Buckets** use hyphens: `my-bucket`. - -## Schema (Structured Streams) - -Schema is a JSON object with a `fields` array; each field has `name`, `type`, `required`. - -```json -{ - "fields": [ - { "name": "event_id", "type": "string", "required": true }, - { "name": "amount", "type": "float64", "required": false } - ] -} -``` - -Field types include `string`, `bool`, `int32/64`, `float32/64`, `timestamp`, `json`, `binary`, `list`, `struct` (with nested `items`/`fields`). For the authoritative type list, see `https://developers.cloudflare.com/pipelines/sql-reference/sql-data-types/`. - -Unstructured streams (no schema) store everything in a single `value` column. - -> Pipelines auto-adds `__ingest_ts` (TIMESTAMP, day-partitioned). Do **not** include it in your schema. - -## Option A: Interactive (Simplest) - -```bash -npx wrangler pipelines setup # creates stream + sink + pipeline, optionally bucket + catalog -``` - -## Option B: Wrangler CLI (Explicit) - -```bash -# 1. Stream -npx wrangler pipelines streams create my_stream --schema-file schema.json - -# 2. Sink — R2 Data Catalog (Iceberg). Creates the namespace + table. -npx wrangler pipelines sinks create my_sink \ - --type r2-data-catalog \ - --bucket my-bucket --namespace my_namespace --table my_table \ - --catalog-token $API_TOKEN \ - --compression zstd --roll-interval 300 - -# 2b. Sink — R2 raw Parquet (alternative) -npx wrangler pipelines sinks create my_sink \ - --type r2 --bucket my-bucket --format parquet \ - --path analytics/events --partitioning "year=%Y/month=%m/day=%d" \ - --access-key-id $KEY --secret-access-key $SECRET - -# 3. Pipeline (SQL connects stream → sink) -npx wrangler pipelines create my_pipeline \ - --sql "INSERT INTO my_sink SELECT * FROM my_stream" -``` - -Tuning knobs (`--compression`, `--roll-interval`, `--roll-size`, etc.) and their allowed values/defaults change — pull the wrangler-commands and sinks docs rather than hardcoding. Rule of thumb: prod `--roll-interval 300+`, dev `10` (creates many small files). - -> **⚠️ Pipelines are immutable.** SQL, schema, and sink config can't be changed — delete and recreate. - -## Option C: REST API (Programmatic) - -Base: `https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pipelines/v1` - -```bash -# Stream -curl -X POST "$BASE_URL/streams" -H "Authorization: Bearer $API_TOKEN" \ - -H "Content-Type: application/json" -d '{ - "name": "my_stream", - "http": {"enabled": true, "authentication": false}, - "schema": {"fields": [{"name": "event_id", "type": "string", "required": true}]} - }' - -# Sink — NOTE REST field names differ from CLI flags (see table) -curl -X POST "$BASE_URL/sinks" -H "Authorization: Bearer $API_TOKEN" \ - -H "Content-Type: application/json" -d '{ - "name": "my_sink", "type": "r2_data_catalog", - "config": {"bucket": "my-bucket", "namespace": "my_namespace", - "table_name": "my_table", "token": "'$API_TOKEN'", - "rolling_policy": {"interval_seconds": 300}}, - "format": {"type": "parquet"} - }' - -# Pipeline -curl -X POST "$BASE_URL/pipelines" -H "Authorization: Bearer $API_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"name": "my_pipeline", "sql": "INSERT INTO my_sink SELECT * FROM my_stream;"}' -``` - -**REST field names ≠ CLI flags** (common failure — not obvious from docs): - -| REST (config body) | CLI flag | Gotcha | -|--------------------|----------|--------| -| `"type": "r2_data_catalog"` | `--type r2-data-catalog` | underscores vs hyphens | -| `"table_name"` | `--table` | different key | -| `"token"` | `--catalog-token` | different key | -| `"format": {"type": "parquet"}` | (implied) | required in REST, omitted in CLI | - -## Worker Binding - -```jsonc -// wrangler.jsonc -{ "pipelines": [ { "stream": "", "binding": "MY_STREAM" } ] } -``` - -> Binding field is `"stream"` as of June 2026 (was `"pipeline"`, still accepted). Use the **stream ID** (`wrangler pipelines streams list`), not the pipeline ID. Redeploy after adding. Generate typed bindings with `npx wrangler types` → `Pipeline` from `cloudflare:pipelines`. - -## Terraform - -Resources: `cloudflare_pipeline_stream`, `cloudflare_pipeline_sink`, `cloudflare_pipeline`. For current attribute schemas pull `https://developers.cloudflare.com/pipelines/reference/terraform/`. - -```hcl -resource "cloudflare_pipeline_stream" "my_stream" { - account_id = var.cloudflare_account_id - name = "my_stream" - format = { type = "json" } - schema = { fields = [{ name = "value", type = "json", required = true }] } - http = { enabled = true, authentication = false, cors = {} } - worker_binding = { enabled = false } -} - -resource "cloudflare_pipeline_sink" "my_sink" { - account_id = var.cloudflare_account_id - name = "my_sink" - type = "r2_data_catalog" - format = { type = "parquet" } - schema = { fields = [] } - config = { - account_id = var.cloudflare_account_id - bucket = cloudflare_r2_bucket.pipeline_bucket.name - table_name = "my_table" - token = var.catalog_token - } -} - -resource "cloudflare_pipeline" "my_pipeline" { - account_id = var.cloudflare_account_id - name = "my_pipeline" - sql = "INSERT INTO ${cloudflare_pipeline_sink.my_sink.name} SELECT * FROM ${cloudflare_pipeline_stream.my_stream.name}" -} -``` - -## Credentials - -| Type | Permission | -|------|------------| -| Catalog token (Iceberg sink) | R2 Storage Admin R&W + R2 Data Catalog R&W | -| R2 credentials (raw sink) | Object Read & Write | -| HTTP ingest token | Workers Pipelines Send (only if stream auth enabled) | - -## See Also - -- [api.md](api.md) — sending events, REST API, lifecycle · [gotchas.md](gotchas.md) — immutability, REST≠CLI diff --git a/external/develop/devops/cloudflare/references/pipelines/gotchas.md b/external/develop/devops/cloudflare/references/pipelines/gotchas.md deleted file mode 100644 index 9770c63..0000000 --- a/external/develop/devops/cloudflare/references/pipelines/gotchas.md +++ /dev/null @@ -1,58 +0,0 @@ -# Pipelines Gotchas - -Non-obvious failure modes (not well covered by docs). For current limits and error semantics, pull `https://developers.cloudflare.com/pipelines/platform/limits/`. - -## Events accepted but never appear (most common) - -HTTP 200 / `send()` resolves, but no data in the sink. Causes: - -1. **Schema validation failure** — structured streams accept then **silently drop** invalid events during processing. Validate client-side (Zod) and monitor `pipelinesUserErrorsAdaptiveGroups`. -2. **First-flush warm-up** — first data takes **3–7 minutes** (warm-up + namespace/table creation) even with `--roll-interval 10`. Poll ≥5 min in tests. -3. **Roll interval not elapsed** — default 300s. -4. **Silent sink failure** — deleted bucket or expired token. Check `recordsWritten > 0` but `filesWritten = 0`; inspect `failure_reason` via `GET /pipelines/{id}`. - -## Everything is immutable - -Cannot modify stream schema, pipeline SQL, or sink config — delete and recreate. Use version naming (`events_v1`) and keep SQL in version control. - -```bash -curl -X DELETE "$BASE_URL/pipelines/{id}" -H "Authorization: Bearer $API_TOKEN" -curl -X DELETE "$BASE_URL/sinks/{id}" -H "Authorization: Bearer $API_TOKEN" -curl -X DELETE "$BASE_URL/streams/{id}" -H "Authorization: Bearer $API_TOKEN" -``` - -## Worker binding undefined (`env.MY_STREAM`) - -1. Use the **stream ID**, not pipeline ID, in `wrangler.jsonc`. -2. Binding field is `"stream"` (June 2026); old `"pipeline"` still works. -3. Redeploy after adding the binding. - -## REST API field names ≠ CLI flags - -`r2_data_catalog` vs `--type r2-data-catalog`, `table_name` vs `--table`, `token` vs `--catalog-token`, and `format` is required in REST but implied in CLI. See [configuration.md](configuration.md#option-c-rest-api-programmatic). - -## `wrangler pipelines delete` defaults to "no" - -Non-interactive environments answer "no" automatically — use REST `DELETE` for CI/automation. - -## Behavioral Notes - -- **`__ingest_ts` auto-added** (TIMESTAMP, day-partitioned). Don't put it in your schema. -- **Sinks can't target existing tables** — the sink creates its own. Use PySpark to write to existing tables. -- **JSON-only input** — no Avro/Protobuf/CSV. -- **Naming:** streams/sinks/pipelines use underscores; buckets use hyphens. -- **Metrics lag 5–10 min** after creation. -- **Pipeline SQL is row-level only** — no GROUP BY/aggregation/window functions (do aggregation in [R2 SQL](../r2-sql/) at query time). CTEs and `UNNEST` are supported. - -## Debug Checklist - -- [ ] Stream exists: `wrangler pipelines streams list` -- [ ] Pipeline `running` (not `initializing`/`failed`): `GET /pipelines/{id}`, check `failure_reason` -- [ ] SQL matches schema; sink token valid; bucket + catalog exist -- [ ] Worker redeployed; binding uses **stream ID** under `"stream"` -- [ ] Waited ≥5 min (first flush) -- [ ] Sink metrics: `filesWritten > 0`; error metrics show no drops - -## See Also - -- [configuration.md](configuration.md) · [api.md](api.md) · [patterns.md](patterns.md) diff --git a/external/develop/devops/cloudflare/references/pipelines/patterns.md b/external/develop/devops/cloudflare/references/pipelines/patterns.md deleted file mode 100644 index 42ed20a..0000000 --- a/external/develop/devops/cloudflare/references/pipelines/patterns.md +++ /dev/null @@ -1,130 +0,0 @@ -# Pipelines Patterns - -Code-first patterns. For observability dataset/field schemas and Logpush dataset lists, pull `https://developers.cloudflare.com/pipelines/observability/metrics/` and `https://developers.cloudflare.com/pipelines/streams/logpush/`. - -## Fire-and-Forget Producer - -```typescript -export default { - async fetch(req, env, ctx) { - const event = { event_id: crypto.randomUUID(), event_type: "page_view", timestamp: new Date().toISOString() }; - ctx.waitUntil(env.MY_STREAM.send([event])); // don't block the response - return new Response("OK"); - } -}; -``` - -## Client-Side Validation with Zod - -Structured streams drop invalid events silently during processing. Validate before sending for immediate feedback. - -```typescript -import { z } from "zod"; - -const EventSchema = z.object({ - event_id: z.string(), - category: z.enum(["purchase", "view"]), - amount: z.number().positive().optional(), -}); - -const validated = EventSchema.parse(rawEvent); // throws synchronously -await env.MY_STREAM.send([validated]); -``` - -## Scheduled Collector Worker - -```jsonc -// wrangler.jsonc -{ - "name": "collector", - "pipelines": [{ "stream": "", "binding": "EVENT_STREAM" }], - "triggers": { "crons": ["*/5 * * * *"] } -} -``` - -```typescript -export default { - async scheduled(event, env, ctx) { - const items = await (await fetch("https://api.example.com/data")).json(); - const events = items.map(i => ({ - event_id: crypto.randomUUID(), - timestamp: new Date().toISOString(), - category: i.type, amount: i.value, - })); - await env.EVENT_STREAM.send(events); - }, -}; -``` - -## Logpush → Pipelines - -Pipelines is a native Logpush destination — ingest Cloudflare logs, transform with SQL, store as Iceberg/Parquet. For the current supported dataset list and field names, pull the Logpush doc above. - -```sql -INSERT INTO http_logs_sink -SELECT - ClientIP, - EdgeResponseStatus, - to_timestamp_micros(EdgeStartTimestamp) AS event_time, - upper(ClientRequestMethod) AS method, - sha256(ClientIP) AS hashed_ip -- redact PII at ingest -FROM http_logs_stream -WHERE EdgeResponseStatus >= 400; -``` - -Configure via Dashboard (**Logpush → Create a job → Pipelines** destination) or API. - -## Pipelines + Queues Fan-out - -```typescript -await Promise.all([ - env.ANALYTICS_STREAM.send([event]), // long-term storage + SQL - env.PROCESS_QUEUE.send(event), // immediate processing + retries -]); -``` - -Use Pipelines for long-term storage + SQL; Queues for immediate processing/retries/DLQ; both for fan-out. - -## Observability (GraphQL Analytics) - -Same R2 API token works. Endpoint: `https://api.cloudflare.com/client/v4/graphql`. Datasets cover ingestion, processing (incl. `decodeErrors`), delivery, sink writes (`filesWritten`), and user/validation errors — see the metrics doc for the full dataset/field catalog. - -```bash -curl -X POST "https://api.cloudflare.com/client/v4/graphql" \ - -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \ - -d '{"query": "query { viewer { accounts(filter: {accountTag: \"'$ACCOUNT_ID'\"}) { pipelinesIngestionAdaptiveGroups(filter: {pipelineId: \"PIPELINE-UUID-WITH-DASHES\", datetime_geq: \"2026-03-01T00:00:00Z\"}, limit: 10) { sum { ingestedRecords ingestedBytes } dimensions { datetimeHour } } } } }"}' -``` - -> **Sink/pipeline IDs need dashes for GraphQL** but wrangler may show them without: `b909fe6e544844abbd63f6dcbc81d602` → `b909fe6e-5448-44ab-bd63-f6dcbc81d602`. Metrics take 5–10 min to populate. - -### Detecting Silent Data Loss - -If a sink's bucket is deleted or its token expires, events are accepted but lost. Tell-tale: `recordsWritten > 0` but `filesWritten = 0`. Always verify data lands in R2 within the roll interval and R2 SQL returns expected counts. - -## Schema Evolution (Immutable Pipelines) - -Pipelines can't change. Version + dual-write: - -```bash -npx wrangler pipelines streams create events_v2 --schema-file v2.json -``` -```typescript -await Promise.all([env.EVENTS_V1.send([event]), env.EVENTS_V2.send([event])]); -// query across versions with UNION ALL in R2 SQL -``` - -## End-to-End: Streaming Analytics Dashboard - -``` -External APIs → Collector Worker (cron) → Pipeline → R2 (Iceberg) → Dashboard Worker → R2 SQL -``` - -1. Create bucket + enable catalog ([r2-data-catalog](../r2-data-catalog/configuration.md)) -2. Create stream + sink + pipeline (here) -3. Collector Worker with cron + stream binding (above) -4. Dashboard Worker querying R2 SQL ([r2-sql/patterns.md](../r2-sql/patterns.md)) -5. Enable automatic compaction - -## See Also - -- [configuration.md](configuration.md) · [api.md](api.md) · [gotchas.md](gotchas.md) · [r2-sql](../r2-sql/) diff --git a/external/develop/devops/cloudflare/references/pulumi/README.md b/external/develop/devops/cloudflare/references/pulumi/README.md deleted file mode 100644 index d7cbf06..0000000 --- a/external/develop/devops/cloudflare/references/pulumi/README.md +++ /dev/null @@ -1,100 +0,0 @@ -# Cloudflare Pulumi Provider - -Expert guidance for Cloudflare Pulumi Provider (@pulumi/cloudflare). - -## Overview - -Programmatic management of Cloudflare resources: Workers, Pages, D1, KV, R2, DNS, Queues, etc. - -**Packages:** -- TypeScript/JS: `@pulumi/cloudflare` -- Python: `pulumi-cloudflare` -- Go: `github.com/pulumi/pulumi-cloudflare/sdk/v6/go/cloudflare` -- .NET: `Pulumi.Cloudflare` - -**Version:** v6.x - -## Core Principles - -1. Use API tokens (not legacy API keys) -2. Store accountId in stack config -3. Match binding names across code/config -4. Use `module: true` for ES modules -5. Set `compatibilityDate` to lock behavior - -## Authentication - -```typescript -import * as cloudflare from "@pulumi/cloudflare"; - -// API Token (recommended): CLOUDFLARE_API_TOKEN env -const provider = new cloudflare.Provider("cf", { apiToken: process.env.CLOUDFLARE_API_TOKEN }); - -// API Key (legacy): CLOUDFLARE_API_KEY + CLOUDFLARE_EMAIL env -const provider = new cloudflare.Provider("cf", { apiKey: process.env.CLOUDFLARE_API_KEY, email: process.env.CLOUDFLARE_EMAIL }); - -// API User Service Key: CLOUDFLARE_API_USER_SERVICE_KEY env -const provider = new cloudflare.Provider("cf", { apiUserServiceKey: process.env.CLOUDFLARE_API_USER_SERVICE_KEY }); -``` - -## Setup - -**Pulumi.yaml:** -```yaml -name: my-cloudflare-app -runtime: nodejs -config: - cloudflare:apiToken: - value: ${CLOUDFLARE_API_TOKEN} -``` - -**Pulumi..yaml:** -```yaml -config: - cloudflare:accountId: "abc123..." -``` - -**index.ts:** -```typescript -import * as pulumi from "@pulumi/pulumi"; -import * as cloudflare from "@pulumi/cloudflare"; -const accountId = new pulumi.Config("cloudflare").require("accountId"); -``` - -## Common Resource Types -- `Provider` - Provider config -- `WorkerScript` - Worker -- `WorkersKvNamespace` - KV -- `R2Bucket` - R2 -- `D1Database` - D1 -- `Queue` - Queue -- `PagesProject` - Pages -- `DnsRecord` - DNS -- `WorkerRoute` - Worker route -- `WorkersDomain` - Custom domain - -## Key Properties -- `accountId` - Required for most resources -- `zoneId` - Required for DNS/domain -- `name`/`title` - Resource identifier -- `*Bindings` - Connect resources to Workers - -## Reading Order - -| Order | File | What | When to Read | -|-------|------|------|--------------| -| 1 | [configuration.md](./configuration.md) | Resource config for Workers/KV/D1/R2/Queues/Pages | First time setup, resource reference | -| 2 | [patterns.md](./patterns.md) | Architecture patterns, multi-env, component resources | Building complex apps, best practices | -| 3 | [api.md](./api.md) | Outputs, dependencies, imports, dynamic providers | Advanced features, integrations | -| 4 | [gotchas.md](./gotchas.md) | Common errors, troubleshooting, limits | Debugging, deployment issues | - -## In This Reference -- [configuration.md](./configuration.md) - Provider config, stack setup, Workers/bindings -- [api.md](./api.md) - Resource types, Workers script, KV/D1/R2/queues/Pages -- [patterns.md](./patterns.md) - Multi-env, secrets, CI/CD, stack management -- [gotchas.md](./gotchas.md) - State issues, deployment failures, limits - -## See Also -- [terraform](../terraform/) - Alternative IaC for Cloudflare -- [wrangler](https://developers.cloudflare.com/workers/wrangler/) - CLI deployment alternative -- [workers](https://developers.cloudflare.com/workers/) - Worker runtime documentation diff --git a/external/develop/devops/cloudflare/references/pulumi/api.md b/external/develop/devops/cloudflare/references/pulumi/api.md deleted file mode 100644 index 332cfef..0000000 --- a/external/develop/devops/cloudflare/references/pulumi/api.md +++ /dev/null @@ -1,200 +0,0 @@ -# API & Data Sources - -## Outputs and Exports - -Export resource identifiers: - -```typescript -export const kvId = kv.id; -export const bucketName = bucket.name; -export const workerUrl = worker.subdomain; -export const dbId = db.id; -``` - -## Resource Dependencies - -Implicit dependencies via outputs: - -```typescript -const kv = new cloudflare.WorkersKvNamespace("kv", { - accountId: accountId, - title: "my-kv", -}); - -// Worker depends on KV (implicit via kv.id) -const worker = new cloudflare.WorkerScript("worker", { - accountId: accountId, - name: "my-worker", - content: code, - kvNamespaceBindings: [{name: "MY_KV", namespaceId: kv.id}], // Creates dependency -}); -``` - -Explicit dependencies: - -```typescript -const migration = new command.local.Command("migration", { - create: pulumi.interpolate`wrangler d1 execute ${db.name} --file ./schema.sql`, -}, {dependsOn: [db]}); - -const worker = new cloudflare.WorkerScript("worker", { - accountId: accountId, - name: "worker", - content: code, - d1DatabaseBindings: [{name: "DB", databaseId: db.id}], -}, {dependsOn: [migration]}); // Ensure migrations run first -``` - -## Using Outputs with API Calls - -```typescript -const db = new cloudflare.D1Database("db", {accountId, name: "my-db"}); - -db.id.apply(async (dbId) => { - const response = await fetch( - `https://api.cloudflare.com/client/v4/accounts/${accountId}/d1/database/${dbId}/query`, - {method: "POST", headers: {"Authorization": `Bearer ${apiToken}`, "Content-Type": "application/json"}, - body: JSON.stringify({sql: "CREATE TABLE users (id INT)"})} - ); - return response.json(); -}); -``` - -## Custom Dynamic Providers - -For resources not in provider: - -```typescript -import * as pulumi from "@pulumi/pulumi"; - -class D1MigrationProvider implements pulumi.dynamic.ResourceProvider { - async create(inputs: any): Promise { - const response = await fetch( - `https://api.cloudflare.com/client/v4/accounts/${inputs.accountId}/d1/database/${inputs.databaseId}/query`, - {method: "POST", headers: {"Authorization": `Bearer ${inputs.apiToken}`, "Content-Type": "application/json"}, - body: JSON.stringify({sql: inputs.sql})} - ); - return {id: `${inputs.databaseId}-${Date.now()}`, outs: await response.json()}; - } - async update(id: string, olds: any, news: any): Promise { - if (olds.sql !== news.sql) await this.create(news); - return {}; - } - async delete(id: string, props: any): Promise {} -} - -class D1Migration extends pulumi.dynamic.Resource { - constructor(name: string, args: any, opts?: pulumi.CustomResourceOptions) { - super(new D1MigrationProvider(), name, args, opts); - } -} - -const migration = new D1Migration("migration", { - accountId, databaseId: db.id, apiToken, sql: "CREATE TABLE users (id INT)", -}, {dependsOn: [db]}); -``` - -## Data Sources - -**Get Zone:** -```typescript -const zone = cloudflare.getZone({name: "example.com"}); -const zoneId = zone.then(z => z.id); -``` - -**Get Accounts (via API):** -Use Cloudflare API directly or custom dynamic resources. - -## Import Existing Resources - -```bash -# Import worker -pulumi import cloudflare:index/workerScript:WorkerScript my-worker / - -# Import KV namespace -pulumi import cloudflare:index/workersKvNamespace:WorkersKvNamespace my-kv - -# Import R2 bucket -pulumi import cloudflare:index/r2Bucket:R2Bucket my-bucket / - -# Import D1 database -pulumi import cloudflare:index/d1Database:D1Database my-db / - -# Import DNS record -pulumi import cloudflare:index/dnsRecord:DnsRecord my-record / -``` - -## Secrets Management - -```typescript -import * as pulumi from "@pulumi/pulumi"; - -const config = new pulumi.Config(); -const apiKey = config.requireSecret("apiKey"); // Encrypted in state - -const worker = new cloudflare.WorkerScript("worker", { - accountId: accountId, - name: "my-worker", - content: code, - secretTextBindings: [{name: "API_KEY", text: apiKey}], -}); -``` - -Store secrets: -```bash -pulumi config set --secret apiKey "secret-value" -``` - -## Transform Pattern - -Modify resource args before creation: - -```typescript -import {Transform} from "@pulumi/pulumi"; - -interface BucketArgs { - accountId: pulumi.Input; - transform?: {bucket?: Transform}; -} - -function createBucket(name: string, args: BucketArgs) { - const bucketArgs: cloudflare.R2BucketArgs = { - accountId: args.accountId, - name: name, - location: "auto", - }; - const finalArgs = args.transform?.bucket?.(bucketArgs) ?? bucketArgs; - return new cloudflare.R2Bucket(name, finalArgs); -} -``` - -## v6.x Worker Versioning Resources - -**Worker** - Container for versions: -```typescript -const worker = new cloudflare.Worker("api", {accountId, name: "api-worker"}); -export const workerId = worker.id; -``` - -**WorkerVersion** - Immutable code + config: -```typescript -const version = new cloudflare.WorkerVersion("v1", { - accountId, workerId: worker.id, - content: fs.readFileSync("./dist/worker.js", "utf8"), - compatibilityDate: "2025-01-01", -}); -export const versionId = version.id; -``` - -**WorkersDeployment** - Active deployment with bindings: -```typescript -const deployment = new cloudflare.WorkersDeployment("prod", { - accountId, workerId: worker.id, versionId: version.id, - kvNamespaceBindings: [{name: "MY_KV", namespaceId: kv.id}], -}); -``` - -**Use:** Advanced deployments (canary, blue-green). Most apps should use `WorkerScript` (auto-versioning). - ---- -See: [README.md](./README.md), [configuration.md](./configuration.md), [patterns.md](./patterns.md), [gotchas.md](./gotchas.md) diff --git a/external/develop/devops/cloudflare/references/pulumi/configuration.md b/external/develop/devops/cloudflare/references/pulumi/configuration.md deleted file mode 100644 index 449419d..0000000 --- a/external/develop/devops/cloudflare/references/pulumi/configuration.md +++ /dev/null @@ -1,198 +0,0 @@ -# Resource Configuration - -## Workers (cloudflare.WorkerScript) - -```typescript -import * as cloudflare from "@pulumi/cloudflare"; -import * as fs from "fs"; - -const worker = new cloudflare.WorkerScript("my-worker", { - accountId: accountId, - name: "my-worker", - content: fs.readFileSync("./dist/worker.js", "utf8"), - module: true, // ES modules - compatibilityDate: "2025-01-01", - compatibilityFlags: ["nodejs_compat"], - - // v6.x: Observability - logpush: true, // Enable Workers Logpush - tailConsumers: [{service: "log-consumer"}], // Stream logs to Worker - - // v6.x: Placement - placement: {mode: "smart"}, // Smart placement for latency optimization - - // Bindings - kvNamespaceBindings: [{name: "MY_KV", namespaceId: kv.id}], - r2BucketBindings: [{name: "MY_BUCKET", bucketName: bucket.name}], - d1DatabaseBindings: [{name: "DB", databaseId: db.id}], - queueBindings: [{name: "MY_QUEUE", queue: queue.id}], - serviceBindings: [{name: "OTHER_SERVICE", service: other.name}], - plainTextBindings: [{name: "ENV_VAR", text: "value"}], - secretTextBindings: [{name: "API_KEY", text: secret}], - - // v6.x: Advanced bindings - analyticsEngineBindings: [{name: "ANALYTICS", dataset: "my-dataset"}], - browserBinding: {name: "BROWSER"}, // Browser Rendering - aiBinding: {name: "AI"}, // Workers AI - hyperdriveBindings: [{name: "HYPERDRIVE", id: hyperdriveConfig.id}], -}); -``` - -## Workers KV (cloudflare.WorkersKvNamespace) - -```typescript -const kv = new cloudflare.WorkersKvNamespace("my-kv", { - accountId: accountId, - title: "my-kv-namespace", -}); - -// Write values -const kvValue = new cloudflare.WorkersKvValue("config", { - accountId: accountId, - namespaceId: kv.id, - key: "config", - value: JSON.stringify({foo: "bar"}), -}); -``` - -## R2 Buckets (cloudflare.R2Bucket) - -```typescript -const bucket = new cloudflare.R2Bucket("my-bucket", { - accountId: accountId, - name: "my-bucket", - location: "auto", // or "wnam", etc. -}); -``` - -## D1 Databases (cloudflare.D1Database) - -```typescript -const db = new cloudflare.D1Database("my-db", {accountId, name: "my-database"}); - -// Migrations via wrangler -import * as command from "@pulumi/command"; -const migration = new command.local.Command("d1-migration", { - create: pulumi.interpolate`wrangler d1 execute ${db.name} --file ./schema.sql`, -}, {dependsOn: [db]}); -``` - -## Queues (cloudflare.Queue) - -```typescript -const queue = new cloudflare.Queue("my-queue", {accountId, name: "my-queue"}); - -// Producer -const producer = new cloudflare.WorkerScript("producer", { - accountId, name: "producer", content: code, - queueBindings: [{name: "MY_QUEUE", queue: queue.id}], -}); - -// Consumer -const consumer = new cloudflare.WorkerScript("consumer", { - accountId, name: "consumer", content: code, - queueConsumers: [{queue: queue.name, maxBatchSize: 10, maxRetries: 3}], -}); -``` - -## Pages Projects (cloudflare.PagesProject) - -```typescript -const pages = new cloudflare.PagesProject("my-site", { - accountId, name: "my-site", productionBranch: "main", - buildConfig: {buildCommand: "npm run build", destinationDir: "dist"}, - source: { - type: "github", - config: {owner: "my-org", repoName: "my-repo", productionBranch: "main"}, - }, - deploymentConfigs: { - production: { - environmentVariables: {NODE_VERSION: "18"}, - kvNamespaces: {MY_KV: kv.id}, - d1Databases: {DB: db.id}, - }, - }, -}); -``` - -## DNS Records (cloudflare.DnsRecord) - -```typescript -const zone = cloudflare.getZone({name: "example.com"}); -const record = new cloudflare.DnsRecord("www", { - zoneId: zone.then(z => z.id), name: "www", type: "A", - content: "192.0.2.1", ttl: 3600, proxied: true, -}); -``` - -## Workers Domains/Routes - -```typescript -// Route (pattern-based) -const route = new cloudflare.WorkerRoute("my-route", { - zoneId: zoneId, - pattern: "example.com/api/*", - scriptName: worker.name, -}); - -// Domain (dedicated subdomain) -const domain = new cloudflare.WorkersDomain("my-domain", { - accountId: accountId, - hostname: "api.example.com", - service: worker.name, - zoneId: zoneId, -}); -``` - -## Assets Configuration (v6.x) - -Serve static assets from Workers: - -```typescript -const worker = new cloudflare.WorkerScript("app", { - accountId: accountId, - name: "my-app", - content: code, - assets: { - path: "./public", // Local directory - // Assets uploaded and served from Workers - }, -}); -``` - -## v6.x Versioned Deployments (Advanced) - -For gradual rollouts, use 3-resource pattern: - -```typescript -// 1. Worker (container for versions) -const worker = new cloudflare.Worker("api", { - accountId: accountId, - name: "api-worker", -}); - -// 2. Version (immutable code + config) -const version = new cloudflare.WorkerVersion("v1", { - accountId: accountId, - workerId: worker.id, - content: fs.readFileSync("./dist/worker.js", "utf8"), - compatibilityDate: "2025-01-01", - compatibilityFlags: ["nodejs_compat"], - // Note: Bindings configured at deployment level -}); - -// 3. Deployment (version + bindings + traffic split) -const deployment = new cloudflare.WorkersDeployment("prod", { - accountId: accountId, - workerId: worker.id, - versionId: version.id, - // Bindings applied to deployment - kvNamespaceBindings: [{name: "MY_KV", namespaceId: kv.id}], -}); -``` - -**When to use:** Blue-green deployments, canary releases, gradual rollouts -**When NOT to use:** Simple single-version deployments (use WorkerScript) - ---- -See: [README.md](./README.md), [api.md](./api.md), [patterns.md](./patterns.md), [gotchas.md](./gotchas.md) diff --git a/external/develop/devops/cloudflare/references/pulumi/gotchas.md b/external/develop/devops/cloudflare/references/pulumi/gotchas.md deleted file mode 100644 index 7a0570f..0000000 --- a/external/develop/devops/cloudflare/references/pulumi/gotchas.md +++ /dev/null @@ -1,181 +0,0 @@ -# Troubleshooting & Best Practices - -## Common Errors - -### "No bundler/build step" - Pulumi uploads raw code - -**Problem:** Worker fails with "Cannot use import statement outside a module" -**Cause:** Pulumi doesn't bundle Worker code - uploads exactly what you provide -**Solution:** Build Worker BEFORE Pulumi deploy - -```typescript -// WRONG: Pulumi won't bundle this -const worker = new cloudflare.WorkerScript("worker", { - content: fs.readFileSync("./src/index.ts", "utf8"), // Raw TS file -}); - -// RIGHT: Build first, then deploy -import * as command from "@pulumi/command"; -const build = new command.local.Command("build", { - create: "npm run build", - dir: "./worker", -}); -const worker = new cloudflare.WorkerScript("worker", { - content: build.stdout.apply(() => fs.readFileSync("./worker/dist/index.js", "utf8")), -}, {dependsOn: [build]}); -``` - -### "wrangler.toml not consumed" - Config drift - -**Problem:** Local wrangler dev works, Pulumi deploy fails -**Cause:** Pulumi ignores wrangler.toml - must duplicate config -**Solution:** Generate wrangler.toml from Pulumi or keep synced manually - -```typescript -// Pattern: Export Pulumi config to wrangler.toml -const workerConfig = { - name: "my-worker", - compatibilityDate: "2025-01-01", - compatibilityFlags: ["nodejs_compat"], -}; - -new command.local.Command("generate-wrangler", { - create: pulumi.interpolate`cat > wrangler.toml <.yaml -config: - cloudflare:accountId: "abc123..." -``` - -### "Binding name mismatch" - -**Problem:** Worker fails with "env.MY_KV is undefined" -**Cause:** Binding name in Pulumi != name in Worker code -**Solution:** Match exactly (case-sensitive) - -```typescript -// Pulumi -kvNamespaceBindings: [{name: "MY_KV", namespaceId: kv.id}] - -// Worker code -export default { async fetch(request, env) { await env.MY_KV.get("key"); }} -``` - -### "API token permissions insufficient" - -**Problem:** `Error: authentication error (10000)` -**Cause:** Token lacks required permissions -**Solution:** Grant token permissions: Account.Workers Scripts:Edit, Account.Account Settings:Read - -### "Resource not found after import" - -**Problem:** Imported resource shows as changed on next `pulumi up` -**Cause:** State mismatch between actual resource and Pulumi config -**Solution:** Check property names/types match exactly - -```bash -pulumi import cloudflare:index/workerScript:WorkerScript my-worker / -pulumi preview # If shows changes, adjust Pulumi code to match actual resource -``` - -### "v6.x Worker versioning confusion" - -**Problem:** Worker deployed but not receiving traffic -**Cause:** v6.x requires Worker + WorkerVersion + WorkersDeployment (3 resources) -**Solution:** Use WorkerScript (auto-versioning) OR full versioning pattern - -```typescript -// SIMPLE: WorkerScript auto-versions (default behavior) -const worker = new cloudflare.WorkerScript("worker", { - accountId, name: "my-worker", content: code, -}); - -// ADVANCED: Manual versioning for gradual rollouts (v6.x) -const worker = new cloudflare.Worker("worker", {accountId, name: "my-worker"}); -const version = new cloudflare.WorkerVersion("v1", { - accountId, workerId: worker.id, content: code, compatibilityDate: "2025-01-01", -}); -const deployment = new cloudflare.WorkersDeployment("prod", { - accountId, workerId: worker.id, versionId: version.id, -}); -``` - -## Best Practices - -1. **Always set compatibilityDate** - Locks Worker behavior, prevents breaking changes -2. **Build before deploy** - Pulumi doesn't bundle; use Command resource or CI build step -3. **Match binding names** - Case-sensitive, must match between Pulumi and Worker code -4. **Use dependsOn for migrations** - Ensure D1 migrations run before Worker deploys -5. **Version Worker content** - Add VERSION binding to force redeployment on content changes -6. **Store secrets in stack config** - Use `pulumi config set --secret` for API keys - -## Limits - -| Resource | Limit | Notes | -|----------|-------|-------| -| Worker script size | 10 MB | Includes all dependencies, after compression | -| Worker CPU time | 10ms (free), 30s default / 5min max (paid) | Per request | -| KV keys per namespace | Unlimited | 1000 ops/sec write, 100k ops/sec read | -| R2 storage | Unlimited | Class A ops: 1M/mo free, Class B: 10M/mo free | -| D1 databases | 50,000 per account | Free: 10 per account, 5 GB each | -| Queues | 10,000 per account | Free: 1M ops/day | -| Pages projects | 500 per account | Free: 100 projects | -| API requests | Varies by plan | ~1200 req/5min on free | - -## Resources - -- **Pulumi Registry:** https://www.pulumi.com/registry/packages/cloudflare/ -- **API Docs:** https://www.pulumi.com/registry/packages/cloudflare/api-docs/ -- **GitHub:** https://github.com/pulumi/pulumi-cloudflare -- **Cloudflare Docs:** https://developers.cloudflare.com/ -- **Workers Docs:** https://developers.cloudflare.com/workers/ - ---- -See: [README.md](./README.md), [configuration.md](./configuration.md), [api.md](./api.md), [patterns.md](./patterns.md) diff --git a/external/develop/devops/cloudflare/references/pulumi/patterns.md b/external/develop/devops/cloudflare/references/pulumi/patterns.md deleted file mode 100644 index c843d54..0000000 --- a/external/develop/devops/cloudflare/references/pulumi/patterns.md +++ /dev/null @@ -1,191 +0,0 @@ -# Architecture Patterns - -## Component Resources - -```typescript -class WorkerApp extends pulumi.ComponentResource { - constructor(name: string, args: WorkerAppArgs, opts?) { - super("custom:cloudflare:WorkerApp", name, {}, opts); - const defaultOpts = {parent: this}; - - this.kv = new cloudflare.WorkersKvNamespace(`${name}-kv`, {accountId: args.accountId, title: `${name}-kv`}, defaultOpts); - this.worker = new cloudflare.WorkerScript(`${name}-worker`, { - accountId: args.accountId, name: `${name}-worker`, content: args.workerCode, - module: true, kvNamespaceBindings: [{name: "KV", namespaceId: this.kv.id}], - }, defaultOpts); - this.domain = new cloudflare.WorkersDomain(`${name}-domain`, { - accountId: args.accountId, hostname: args.domain, service: this.worker.name, - }, defaultOpts); - } -} -``` - -## Full-Stack Worker App - -```typescript -const kv = new cloudflare.WorkersKvNamespace("cache", {accountId, title: "api-cache"}); -const db = new cloudflare.D1Database("db", {accountId, name: "app-database"}); -const bucket = new cloudflare.R2Bucket("assets", {accountId, name: "app-assets"}); - -const apiWorker = new cloudflare.WorkerScript("api", { - accountId, name: "api-worker", content: fs.readFileSync("./dist/api.js", "utf8"), - module: true, kvNamespaceBindings: [{name: "CACHE", namespaceId: kv.id}], - d1DatabaseBindings: [{name: "DB", databaseId: db.id}], - r2BucketBindings: [{name: "ASSETS", bucketName: bucket.name}], -}); -``` - -## Multi-Environment Setup - -```typescript -const stack = pulumi.getStack(); -const worker = new cloudflare.WorkerScript(`worker-${stack}`, { - accountId, name: `my-worker-${stack}`, content: code, - plainTextBindings: [{name: "ENVIRONMENT", text: stack}], -}); -``` - -## Queue-Based Processing - -```typescript -const queue = new cloudflare.Queue("processing-queue", {accountId, name: "image-processing"}); - -// Producer: API receives requests -const apiWorker = new cloudflare.WorkerScript("api", { - accountId, name: "api-worker", content: apiCode, - queueBindings: [{name: "PROCESSING_QUEUE", queue: queue.id}], -}); - -// Consumer: Process async -const processorWorker = new cloudflare.WorkerScript("processor", { - accountId, name: "processor-worker", content: processorCode, - queueConsumers: [{queue: queue.name, maxBatchSize: 10, maxRetries: 3, maxWaitTimeMs: 5000}], - r2BucketBindings: [{name: "OUTPUT_BUCKET", bucketName: outputBucket.name}], -}); -``` - -## Microservices with Service Bindings - -```typescript -const authWorker = new cloudflare.WorkerScript("auth", {accountId, name: "auth-service", content: authCode}); -const apiWorker = new cloudflare.WorkerScript("api", { - accountId, name: "api-service", content: apiCode, - serviceBindings: [{name: "AUTH", service: authWorker.name}], -}); -``` - -## Event-Driven Architecture - -```typescript -const eventQueue = new cloudflare.Queue("events", {accountId, name: "event-bus"}); -const producer = new cloudflare.WorkerScript("producer", { - accountId, name: "api-producer", content: producerCode, - queueBindings: [{name: "EVENTS", queue: eventQueue.id}], -}); -const consumer = new cloudflare.WorkerScript("consumer", { - accountId, name: "email-consumer", content: consumerCode, - queueConsumers: [{queue: eventQueue.name, maxBatchSize: 10}], -}); -``` - -## v6.x Versioned Deployments (Blue-Green/Canary) - -```typescript -const worker = new cloudflare.Worker("api", {accountId, name: "api-worker"}); -const v1 = new cloudflare.WorkerVersion("v1", {accountId, workerId: worker.id, content: fs.readFileSync("./dist/v1.js", "utf8"), compatibilityDate: "2025-01-01"}); -const v2 = new cloudflare.WorkerVersion("v2", {accountId, workerId: worker.id, content: fs.readFileSync("./dist/v2.js", "utf8"), compatibilityDate: "2025-01-01"}); - -// Gradual rollout: 10% v2, 90% v1 -const deployment = new cloudflare.WorkersDeployment("canary", { - accountId, workerId: worker.id, - versions: [{versionId: v2.id, percentage: 10}, {versionId: v1.id, percentage: 90}], - kvNamespaceBindings: [{name: "MY_KV", namespaceId: kv.id}], -}); -``` - -**Use:** Canary releases, A/B testing, blue-green. Most apps use `WorkerScript` (auto-versioning). - -## Wrangler.toml Generation (Bridge IaC with Local Dev) - -Generate wrangler.toml from Pulumi config to keep local dev in sync: - -```typescript -import * as command from "@pulumi/command"; - -const workerConfig = { - name: "my-worker", - compatibilityDate: "2025-01-01", - compatibilityFlags: ["nodejs_compat"], -}; - -// Create resources -const kv = new cloudflare.WorkersKvNamespace("kv", {accountId, title: "my-kv"}); -const db = new cloudflare.D1Database("db", {accountId, name: "my-db"}); -const bucket = new cloudflare.R2Bucket("bucket", {accountId, name: "my-bucket"}); - -// Generate wrangler.toml after resources created -const wranglerGen = new command.local.Command("gen-wrangler", { - create: pulumi.interpolate`cat > wrangler.toml < fs.readFileSync("./worker/dist/index.js", "utf8")), -}, {dependsOn: [build]}); -``` - -## Content SHA Pattern (Force Updates) - -Prevent false "no changes" detections: - -```typescript -const version = Date.now().toString(); -const worker = new cloudflare.WorkerScript("worker", { - accountId, name: "my-worker", content: code, - plainTextBindings: [{name: "VERSION", text: version}], // Forces deployment -}); -``` - ---- -See: [README.md](./README.md), [configuration.md](./configuration.md), [api.md](./api.md), [gotchas.md](./gotchas.md) diff --git a/external/develop/devops/cloudflare/references/queues/README.md b/external/develop/devops/cloudflare/references/queues/README.md index 9d36864..fefa7bc 100644 --- a/external/develop/devops/cloudflare/references/queues/README.md +++ b/external/develop/devops/cloudflare/references/queues/README.md @@ -10,7 +10,7 @@ Fetch the relevant documentation below before implementing. Treat current Cloudf - Use an HTTP pull consumer when processing runs in another environment; plan for polling, visibility timeouts, and acknowledgement leases. - Choose a message encoding the consumer can decode. Check serialization and compatibility-date behavior before sending existing application objects. -See [How Queues works](https://developers.cloudflare.com/queues/reference/how-queues-works/) and [delivery guarantees](https://developers.cloudflare.com/queues/reference/delivery-guarantees/) before choosing ordering or deduplication strategies. +See [How Queues works](https://developers.cloudflare.com/queues/reference/how-queues-works/index.md) and [delivery guarantees](https://developers.cloudflare.com/queues/reference/delivery-guarantees/index.md) before choosing ordering or deduplication strategies. ## Read by task @@ -21,4 +21,4 @@ See [How Queues works](https://developers.cloudflare.com/queues/reference/how-qu | Buffer APIs, defer jobs, or integrate with storage and orchestration | [patterns.md](./patterns.md) | | Diagnose delivery failures, duplicates, or capacity issues | [gotchas.md](./gotchas.md) | -For a first application, fetch [Getting started](https://developers.cloudflare.com/queues/get-started/). Retrieve [limits](https://developers.cloudflare.com/queues/platform/limits/) and [pricing](https://developers.cloudflare.com/queues/platform/pricing/) before sizing throughput, retention, or cost; plan-specific values are not maintained here. +For a first application, fetch [Getting started](https://developers.cloudflare.com/queues/get-started/index.md). Retrieve [limits](https://developers.cloudflare.com/queues/platform/limits/index.md) and [pricing](https://developers.cloudflare.com/queues/platform/pricing/index.md) before sizing throughput, retention, or cost; plan-specific values are not maintained here. diff --git a/external/develop/devops/cloudflare/references/queues/api.md b/external/develop/devops/cloudflare/references/queues/api.md index 9fa48b4..9e6faa4 100644 --- a/external/develop/devops/cloudflare/references/queues/api.md +++ b/external/develop/devops/cloudflare/references/queues/api.md @@ -4,10 +4,10 @@ Fetch the current API documentation for the operation being implemented; do not | Task | Documentation | |------|---------------| -| Send individual messages or batches; choose encoding; implement a typed Worker queue handler; dispatch by queue name | [JavaScript APIs](https://developers.cloudflare.com/queues/configuration/javascript-apis/) | -| Understand automatic acknowledgement, explicit per-message and batch actions, precedence, delivery failures, delays, and backoff | [Batching, retries, and delays](https://developers.cloudflare.com/queues/configuration/batching-retries/) | -| Pull over HTTP and acknowledge or retry using leases | [Pull consumers](https://developers.cloudflare.com/queues/configuration/pull-consumers/) | -| Publish from outside Workers | [Publish to a Queue via HTTP](https://developers.cloudflare.com/queues/examples/publish-to-a-queue-via-http/) | +| Send individual messages or batches; choose encoding; implement a typed Worker queue handler; dispatch by queue name | [JavaScript APIs](https://developers.cloudflare.com/queues/configuration/javascript-apis/index.md) | +| Understand automatic acknowledgement, explicit per-message and batch actions, precedence, delivery failures, delays, and backoff | [Batching, retries, and delays](https://developers.cloudflare.com/queues/configuration/batching-retries/index.md) | +| Pull over HTTP and acknowledge or retry using leases | [Pull consumers](https://developers.cloudflare.com/queues/configuration/pull-consumers/index.md) | +| Publish from outside Workers | [Publish to a Queue via HTTP](https://developers.cloudflare.com/queues/examples/publish-to-a-queue-via-http/index.md) | Acknowledge only after the intended work succeeds. For independently processed messages, use per-message outcomes to avoid replaying successful work when another message fails. If catching an error and continuing, explicitly request a retry for work that still needs processing; a successful handler return can acknowledge messages automatically. Fetch the linked acknowledgement rules before mixing message-level and batch-level actions. diff --git a/external/develop/devops/cloudflare/references/queues/configuration.md b/external/develop/devops/cloudflare/references/queues/configuration.md index d32999b..c08779b 100644 --- a/external/develop/devops/cloudflare/references/queues/configuration.md +++ b/external/develop/devops/cloudflare/references/queues/configuration.md @@ -4,17 +4,17 @@ Fetch the relevant guide before writing configuration or running CLI commands. C | Task | Documentation | |------|---------------| -| Create a queue and connect producer and consumer Workers | [Getting started](https://developers.cloudflare.com/queues/get-started/) | -| Configure producer bindings, Worker consumers, retention, and concurrency settings | [Configure Queues](https://developers.cloudflare.com/queues/configuration/configure-queues/) | -| Configure an external HTTP consumer and its visibility timeout | [Pull consumers](https://developers.cloudflare.com/queues/configuration/pull-consumers/) | -| Choose batching, retry policy, or delivery delays | [Batching, retries, and delays](https://developers.cloudflare.com/queues/configuration/batching-retries/) | -| Preserve messages that exhaust retries | [Dead Letter Queues](https://developers.cloudflare.com/queues/configuration/dead-letter-queues/) | -| Set consumer scaling for downstream capacity | [Consumer concurrency](https://developers.cloudflare.com/queues/configuration/consumer-concurrency/) | -| Choose content types and type Worker messages | [JavaScript APIs](https://developers.cloudflare.com/queues/configuration/javascript-apis/) | -| Create, update, attach, remove, or delete queues and consumers | [Wrangler commands](https://developers.cloudflare.com/queues/reference/wrangler-commands/) | -| Pause delivery, resume it, or purge messages | [Pause and purge](https://developers.cloudflare.com/queues/configuration/pause-purge/) | -| Develop and test producers and consumers locally | [Local development](https://developers.cloudflare.com/queues/configuration/local-development/) | +| Create a queue and connect producer and consumer Workers | [Getting started](https://developers.cloudflare.com/queues/get-started/index.md) | +| Configure producer bindings, Worker consumers, retention, and concurrency settings | [Configure Queues](https://developers.cloudflare.com/queues/configuration/configure-queues/index.md) | +| Configure an external HTTP consumer and its visibility timeout | [Pull consumers](https://developers.cloudflare.com/queues/configuration/pull-consumers/index.md) | +| Choose batching, retry policy, or delivery delays | [Batching, retries, and delays](https://developers.cloudflare.com/queues/configuration/batching-retries/index.md) | +| Preserve messages that exhaust retries | [Dead Letter Queues](https://developers.cloudflare.com/queues/configuration/dead-letter-queues/index.md) | +| Set consumer scaling for downstream capacity | [Consumer concurrency](https://developers.cloudflare.com/queues/configuration/consumer-concurrency/index.md) | +| Choose content types and type Worker messages | [JavaScript APIs](https://developers.cloudflare.com/queues/configuration/javascript-apis/index.md) | +| Create, update, attach, remove, or delete queues and consumers | [Wrangler commands](https://developers.cloudflare.com/queues/reference/wrangler-commands/index.md) | +| Pause delivery, resume it, or purge messages | [Pause and purge](https://developers.cloudflare.com/queues/configuration/pause-purge/index.md) | +| Develop and test producers and consumers locally | [Local development](https://developers.cloudflare.com/queues/configuration/local-development/index.md) | Choose push or pull based on where processing runs, then select an encoding supported by that consumer. Tune batching for acceptable latency and downstream write capacity. Decide how failed messages will be inspected and replayed before configuring a dead-letter queue. -Fetch [limits](https://developers.cloudflare.com/queues/platform/limits/) and [pricing](https://developers.cloudflare.com/queues/platform/pricing/) for the account's plan before selecting retention, delays, or capacity. Do not reuse numeric settings from unrelated examples. +Fetch [limits](https://developers.cloudflare.com/queues/platform/limits/index.md) and [pricing](https://developers.cloudflare.com/queues/platform/pricing/index.md) for the account's plan before selecting retention, delays, or capacity. Do not reuse numeric settings from unrelated examples. diff --git a/external/develop/devops/cloudflare/references/queues/gotchas.md b/external/develop/devops/cloudflare/references/queues/gotchas.md index 1165f76..16187d0 100644 --- a/external/develop/devops/cloudflare/references/queues/gotchas.md +++ b/external/develop/devops/cloudflare/references/queues/gotchas.md @@ -4,14 +4,14 @@ Fetch the linked documentation before changing retry policy or interpreting deli | Symptom or question | Documentation and decision | |---------------------|----------------------------| -| Successful work repeats after another message fails | Read [acknowledgement and retry rules](https://developers.cloudflare.com/queues/configuration/batching-retries/); use per-message outcomes for independent work. | -| A caught failure disappears instead of retrying | Read [handler lifecycle and APIs](https://developers.cloudflare.com/queues/configuration/javascript-apis/); returning successfully can acknowledge messages. Explicitly retry failed work when continuing. | -| Duplicate processing | Read [delivery guarantees](https://developers.cloudflare.com/queues/reference/delivery-guarantees/); enforce idempotency at the side-effect destination. | -| Pull consumers cannot decode payloads | Check [pull consumer encoding](https://developers.cloudflare.com/queues/configuration/pull-consumers/) and [content types](https://developers.cloudflare.com/queues/configuration/javascript-apis/) against the producer. | -| Messages stop arriving or backlog grows | Check [consumer configuration](https://developers.cloudflare.com/queues/configuration/configure-queues/), [pause state](https://developers.cloudflare.com/queues/configuration/pause-purge/), and [queue metrics](https://developers.cloudflare.com/queues/observability/metrics/). | -| Dead-letter volume rises or messages disappear after retries | Read [Dead Letter Queues](https://developers.cloudflare.com/queues/configuration/dead-letter-queues/); inspect failures and plan recovery before increasing retries. | -| API errors, resource exhaustion, or CPU failures | Read [error codes](https://developers.cloudflare.com/queues/reference/error-codes/), [limits](https://developers.cloudflare.com/queues/platform/limits/), and [consumer concurrency](https://developers.cloudflare.com/queues/configuration/consumer-concurrency/). | -| Retention, delay, throughput, or cost assumptions no longer hold | Retrieve current [limits](https://developers.cloudflare.com/queues/platform/limits/) and [pricing](https://developers.cloudflare.com/queues/platform/pricing/) for the account's plan. | +| Successful work repeats after another message fails | Read [acknowledgement and retry rules](https://developers.cloudflare.com/queues/configuration/batching-retries/index.md); use per-message outcomes for independent work. | +| A caught failure disappears instead of retrying | Read [handler lifecycle and APIs](https://developers.cloudflare.com/queues/configuration/javascript-apis/index.md); returning successfully can acknowledge messages. Explicitly retry failed work when continuing. | +| Duplicate processing | Read [delivery guarantees](https://developers.cloudflare.com/queues/reference/delivery-guarantees/index.md); enforce idempotency at the side-effect destination. | +| Pull consumers cannot decode payloads | Check [pull consumer encoding](https://developers.cloudflare.com/queues/configuration/pull-consumers/index.md) and [content types](https://developers.cloudflare.com/queues/configuration/javascript-apis/index.md) against the producer. | +| Messages stop arriving or backlog grows | Check [consumer configuration](https://developers.cloudflare.com/queues/configuration/configure-queues/index.md), [pause state](https://developers.cloudflare.com/queues/configuration/pause-purge/index.md), and [queue metrics](https://developers.cloudflare.com/queues/observability/metrics/index.md). | +| Dead-letter volume rises or messages disappear after retries | Read [Dead Letter Queues](https://developers.cloudflare.com/queues/configuration/dead-letter-queues/index.md); inspect failures and plan recovery before increasing retries. | +| API errors, resource exhaustion, or CPU failures | Read [error codes](https://developers.cloudflare.com/queues/reference/error-codes/index.md), [limits](https://developers.cloudflare.com/queues/platform/limits/index.md), and [consumer concurrency](https://developers.cloudflare.com/queues/configuration/consumer-concurrency/index.md). | +| Retention, delay, throughput, or cost assumptions no longer hold | Retrieve current [limits](https://developers.cloudflare.com/queues/platform/limits/index.md) and [pricing](https://developers.cloudflare.com/queues/platform/pricing/index.md) for the account's plan. | Distinguish transient dependency failures from invalid payloads before choosing retry or recovery behavior. Acknowledging a failed message does not send it to a dead-letter queue. If handling a permanent failure separately, persist the intended recovery record successfully before acknowledging; use the documented dead-letter policy when relying on retry exhaustion. diff --git a/external/develop/devops/cloudflare/references/queues/patterns.md b/external/develop/devops/cloudflare/references/queues/patterns.md index bcf7ac4..d199582 100644 --- a/external/develop/devops/cloudflare/references/queues/patterns.md +++ b/external/develop/devops/cloudflare/references/queues/patterns.md @@ -4,18 +4,18 @@ Fetch the guide matching the task and adapt its example to the application's del | Task | Documentation | |------|---------------| -| Accept requests and enqueue asynchronous tasks; publish to multiple queues | [Publish to a Queue via Workers](https://developers.cloudflare.com/queues/examples/publish-to-a-queue-via-workers/) | -| Buffer writes to an external API or defer a job | [Batching, retries, and delays](https://developers.cloudflare.com/queues/configuration/batching-retries/) | -| Handle upstream rate limits and backpressure | [Handle rate limits of external APIs](https://developers.cloudflare.com/queues/tutorials/handle-rate-limits/) and [consumer concurrency](https://developers.cloudflare.com/queues/configuration/consumer-concurrency/) | -| Isolate workloads with different latency or capacity needs | [Configure Queues](https://developers.cloudflare.com/queues/configuration/configure-queues/) | -| Retain exhausted retries for inspection and recovery | [Dead Letter Queues](https://developers.cloudflare.com/queues/configuration/dead-letter-queues/) | -| Process R2 object events | [R2 event notifications](https://developers.cloudflare.com/r2/buckets/event-notifications/) | -| Batch output into R2 | [Use Queues to store data in R2](https://developers.cloudflare.com/queues/examples/send-errors-to-r2/) | -| Batch writes into D1 | [D1 database API](https://developers.cloudflare.com/d1/worker-api/d1-database/) | -| Start durable multi-step jobs | [Trigger Workflows](https://developers.cloudflare.com/workflows/build/trigger-workflows/) | -| Publish from a Durable Object | [Use Queues from Durable Objects](https://developers.cloudflare.com/queues/examples/use-queues-with-durable-objects/) | -| Route consumer work to a Durable Object | [Invoke Durable Object methods](https://developers.cloudflare.com/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/) | +| Accept requests and enqueue asynchronous tasks; publish to multiple queues | [Publish to a Queue via Workers](https://developers.cloudflare.com/queues/examples/publish-to-a-queue-via-workers/index.md) | +| Buffer writes to an external API or defer a job | [Batching, retries, and delays](https://developers.cloudflare.com/queues/configuration/batching-retries/index.md) | +| Handle upstream rate limits and backpressure | [Handle rate limits of external APIs](https://developers.cloudflare.com/queues/tutorials/handle-rate-limits/index.md) and [consumer concurrency](https://developers.cloudflare.com/queues/configuration/consumer-concurrency/index.md) | +| Isolate workloads with different latency or capacity needs | [Configure Queues](https://developers.cloudflare.com/queues/configuration/configure-queues/index.md) | +| Retain exhausted retries for inspection and recovery | [Dead Letter Queues](https://developers.cloudflare.com/queues/configuration/dead-letter-queues/index.md) | +| Process R2 object events | [R2 event notifications](https://developers.cloudflare.com/r2/buckets/event-notifications/index.md) | +| Batch output into R2 | [Use Queues to store data in R2](https://developers.cloudflare.com/queues/examples/send-errors-to-r2/index.md) | +| Batch writes into D1 | [D1 database API](https://developers.cloudflare.com/d1/worker-api/d1-database/index.md) | +| Start durable multi-step jobs | [Trigger Workflows](https://developers.cloudflare.com/workflows/build/trigger-workflows/index.md) | +| Publish from a Durable Object | [Use Queues from Durable Objects](https://developers.cloudflare.com/queues/examples/use-queues-with-durable-objects/index.md) | +| Route consumer work to a Durable Object | [Invoke Durable Object methods](https://developers.cloudflare.com/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/index.md) | -Design side effects for [at-least-once delivery](https://developers.cloudflare.com/queues/reference/delivery-guarantees/). A separate check-then-write deduplication flag is not an atomic guarantee: concurrent delivery or a crash between the side effect and recording completion can repeat work. Prefer idempotency keys or transactional enforcement at the destination. +Design side effects for [at-least-once delivery](https://developers.cloudflare.com/queues/reference/delivery-guarantees/index.md). A separate check-then-write deduplication flag is not an atomic guarantee: concurrent delivery or a crash between the side effect and recording completion can repeat work. Prefer idempotency keys or transactional enforcement at the destination. Acknowledge after the destination confirms success. For fan-out, plan for some sends succeeding before another fails; retries must not duplicate downstream effects. Separate queues can isolate workloads, but do not imply a global priority or ordering guarantee. diff --git a/external/develop/devops/cloudflare/references/r2-data-catalog/README.md b/external/develop/devops/cloudflare/references/r2-data-catalog/README.md deleted file mode 100644 index 399f4f8..0000000 --- a/external/develop/devops/cloudflare/references/r2-data-catalog/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# R2 Data Catalog - -Use R2 Data Catalog for Iceberg analytics and data pipelines on object storage. For transactional application queries, consider a database; for unstructured objects, use [R2](../r2/). - -Distinguish the Iceberg REST catalog used by query engines from Cloudflare's control-plane API for catalog administration. Start with the workflow you need: - -| Task | Reference | -|------|-----------| -| Enable a catalog, discover connection values, and choose credentials | [Configuration](configuration.md) | -| Select administration or engine APIs | [API selection](api.md) | -| Choose a Python, Spark, or SQL workflow | [Patterns](patterns.md) | -| Diagnose authentication, maintenance, or client problems | [Troubleshooting](gotchas.md) | - -Copy the actual **Catalog URI** and **Warehouse name** from the catalog detail page or Wrangler's enable output. Pass both to the selected engine; do not reconstruct them from an assumed bucket naming convention. Retrieve [Manage catalogs](https://developers.cloudflare.com/r2-data-catalog/manage-catalogs/) before setup or permission changes. - -Related workflows: [Pipelines](../pipelines/) for ingest and [R2 SQL](../r2-sql/) for querying tables. diff --git a/external/develop/devops/cloudflare/references/r2-data-catalog/api.md b/external/develop/devops/cloudflare/references/r2-data-catalog/api.md deleted file mode 100644 index ea735a7..0000000 --- a/external/develop/devops/cloudflare/references/r2-data-catalog/api.md +++ /dev/null @@ -1,47 +0,0 @@ -# R2 Data Catalog API Selection - -Use the Iceberg REST catalog through an engine for table reads and writes; use the Cloudflare control-plane API for catalog administration. Copy the catalog connection values from the actual environment as described in [configuration](configuration.md). - -| Task | Documentation | -|------|---------------| -| Enable or disable catalogs; inspect status, credentials, namespaces, tables, and maintenance configuration | [R2 Data Catalog control-plane API](https://developers.cloudflare.com/api/resources/r2_data_catalog/) — select the affected operation for its schema, pagination, and namespace encoding | -| Connect and create tables through Python | [PyIceberg configuration](https://developers.cloudflare.com/r2-data-catalog/config-examples/pyiceberg/) | -| Connect, create, write, and query through Spark | [PySpark configuration](https://developers.cloudflare.com/r2-data-catalog/config-examples/spark-python/) | -| Plan automatic compaction and snapshot expiration | [Table maintenance](https://developers.cloudflare.com/r2-data-catalog/table-maintenance/) | -| Delete rows, tables, or associated files | [Deleting data](https://developers.cloudflare.com/r2-data-catalog/deleting-data/) | - -For engine-specific operations beyond these Cloudflare examples, follow the upstream engine documentation linked from the relevant configuration guide and check the installed version. Do not infer engine method signatures from the control-plane API. - -## Get Table (repository-specific metadata introspection note) - -This existing repository note is retained because the published control-plane API reference does not document this operation or its snapshot-pruning response. Verify availability and response behavior against the target service or authoritative implementation before relying on it; it is not a documented API guarantee. Do not substitute the documented list-tables response for this metadata response. - -`GET /namespaces/{ns}/tables/{table}` returns schema, partition spec, sort order, and snapshot info — like Iceberg "load table" but on the control plane, with snapshots pruned to the most recent 10. - -```bash -curl -s "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/r2-catalog/$BUCKET/namespaces/live/tables/earthquakes" \ - -H "Authorization: Bearer $API_TOKEN" -``` - -```json -{"result": { - "identifier": {"namespace": ["live"], "name": "earthquakes"}, - "table_uuid": "019edccf-3ac8-73e3-...", - "metadata_location": "s3://live-data/__r2_data_catalog/.../metadata/01225-....metadata.json", - "total_snapshots": 1225, - "returned_snapshots": 10, - "metadata": { /* standard Iceberg TableMetadata: schemas, partition-specs, sort-orders, - properties, current-snapshot-id, snapshots (≤10), snapshot-log, refs */ } -}, "success": true} -``` - -| Field | Description | -|-------|-------------| -| `identifier` | `{namespace: [...], name}` | -| `table_uuid` | Iceberg table UUID | -| `metadata_location` | R2 path to current metadata file | -| `total_snapshots` | Total before pruning | -| `returned_snapshots` | Count in `metadata.snapshots` (max 10) | -| `metadata` | Standard [Iceberg TableMetadata](https://iceberg.apache.org/spec/#table-metadata-fields), arrays pruned to 10 | - -See [patterns](patterns.md) for engine selection and [troubleshooting](gotchas.md) for diagnosis. diff --git a/external/develop/devops/cloudflare/references/r2-data-catalog/configuration.md b/external/develop/devops/cloudflare/references/r2-data-catalog/configuration.md deleted file mode 100644 index c249a45..0000000 --- a/external/develop/devops/cloudflare/references/r2-data-catalog/configuration.md +++ /dev/null @@ -1,17 +0,0 @@ -# R2 Data Catalog Configuration - -Inspect the existing bucket, catalog, engine versions, and credential configuration before making changes. Use the project's installed tools and preserve its environment-variable or secret-management conventions. - -| Task | Documentation | -|------|---------------| -| Enable a catalog and obtain connection details | [Enable R2 Data Catalog](https://developers.cloudflare.com/r2-data-catalog/manage-catalogs/#enable-r2-data-catalog-on-a-bucket) | -| Select credentials for readers, writers, or maintenance | [Authenticate your Iceberg engine](https://developers.cloudflare.com/r2-data-catalog/manage-catalogs/#authenticate-your-iceberg-engine) | -| Configure compaction and its service credential | [Enable compaction](https://developers.cloudflare.com/r2-data-catalog/manage-catalogs/#enable-compaction) | -| Configure snapshot retention | [Enable snapshot expiration](https://developers.cloudflare.com/r2-data-catalog/manage-catalogs/#enable-snapshot-expiration) | -| Choose file sizes, retention policy, and maintenance scope | [Table maintenance](https://developers.cloudflare.com/r2-data-catalog/table-maintenance/) | -| Connect a Python client | [PyIceberg](https://developers.cloudflare.com/r2-data-catalog/config-examples/pyiceberg/) | -| Connect Spark | [PySpark](https://developers.cloudflare.com/r2-data-catalog/config-examples/spark-python/) | -| Connect another query engine | [Engine configuration guides](https://developers.cloudflare.com/r2-data-catalog/config-examples/) | -| Disable catalog access | [Disable R2 Data Catalog](https://developers.cloudflare.com/r2-data-catalog/manage-catalogs/#disable-r2-data-catalog-on-a-bucket) | - -Copy the Catalog URI and Warehouse name exactly from the catalog detail page or Wrangler enable output. Scope both catalog and storage permissions to the operations the client needs; readers do not need a blanket write-enabled token. Treat maintenance credentials separately from reader credentials. Verify connectivity with a read operation before attempting writes, then check catalog and credential status through the [control-plane API](api.md). diff --git a/external/develop/devops/cloudflare/references/r2-data-catalog/gotchas.md b/external/develop/devops/cloudflare/references/r2-data-catalog/gotchas.md deleted file mode 100644 index 67008d7..0000000 --- a/external/develop/devops/cloudflare/references/r2-data-catalog/gotchas.md +++ /dev/null @@ -1,18 +0,0 @@ -# R2 Data Catalog Troubleshooting - -Identify whether the failure occurs in catalog administration, engine metadata access, or underlying object access before changing permissions or client settings. - -| Check | Documentation | -|-------|---------------| -| Catalog enablement, Catalog URI, or Warehouse mismatch | [Manage catalogs](https://developers.cloudflare.com/r2-data-catalog/manage-catalogs/) | -| Reader/writer token scope or file-access denial | [Engine authentication](https://developers.cloudflare.com/r2-data-catalog/manage-catalogs/#authenticate-your-iceberg-engine) — inspect both catalog and storage permissions | -| Missing maintenance credentials or wrong table/catalog configuration | [Control-plane API](https://developers.cloudflare.com/api/resources/r2_data_catalog/) and [enable compaction](https://developers.cloudflare.com/r2-data-catalog/manage-catalogs/#enable-compaction) | -| Compaction backlog, retention, or orphaned files | [Table maintenance](https://developers.cloudflare.com/r2-data-catalog/table-maintenance/) | -| PyIceberg connection or table creation | [PyIceberg configuration](https://developers.cloudflare.com/r2-data-catalog/config-examples/pyiceberg/) | -| Spark dependency, credential-vending, or signing configuration | [PySpark configuration](https://developers.cloudflare.com/r2-data-catalog/config-examples/spark-python/) | -| Deleted data is still present | [Deleting data](https://developers.cloudflare.com/r2-data-catalog/deleting-data/) | -| Catalog request or maintenance-job diagnosis | [Metrics and analytics](https://developers.cloudflare.com/r2-data-catalog/observability/metrics/) | - -Compare the client's configured URI and warehouse with the actual catalog values. Test a read operation first; do not grant write access merely to resolve a reader's failure. For schema or concurrency errors, inspect the installed engine's behavior and current table metadata before retrying. The [get-table note](api.md#get-table-repository-specific-metadata-introspection-note) is not a substitute for verifying the service's response contract. - -See [configuration](configuration.md) and [patterns](patterns.md) for implementation choices. diff --git a/external/develop/devops/cloudflare/references/r2-data-catalog/patterns.md b/external/develop/devops/cloudflare/references/r2-data-catalog/patterns.md deleted file mode 100644 index d27e478..0000000 --- a/external/develop/devops/cloudflare/references/r2-data-catalog/patterns.md +++ /dev/null @@ -1,17 +0,0 @@ -# R2 Data Catalog Patterns - -Choose the engine based on the project's existing runtime and workload, then retrieve its current connection example. - -| Need | Starting point | -|------|----------------| -| Python catalog operations and ingestion without a Spark deployment | [PyIceberg](https://developers.cloudflare.com/r2-data-catalog/config-examples/pyiceberg/) | -| Existing Spark ETL and distributed table processing | [PySpark](https://developers.cloudflare.com/r2-data-catalog/config-examples/spark-python/) | -| Connect an existing SQL engine | [Engine configuration guides](https://developers.cloudflare.com/r2-data-catalog/config-examples/) | -| Query through Cloudflare's serverless SQL service | [R2 SQL](../r2-sql/) | -| Stream events into tables | [Pipelines patterns](../pipelines/patterns.md) | - -Use the discovered Catalog URI and Warehouse name from [configuration](configuration.md). Match dependencies to the installed engine and the current guide instead of adopting a universal pinned Spark/Iceberg combination. - -Plan ingestion, query, and maintenance responsibilities together. Prefer [automatic table maintenance](https://developers.cloudflare.com/r2-data-catalog/table-maintenance/) when it meets the workload; align retention with time-travel needs before enabling expiration. For engine-specific partitioning, schema evolution, or manual procedures, consult that engine's linked upstream documentation and verify behavior on representative data. - -When multiple writers share a table, design recovery around the actual failed operation and the engine's commit semantics. Reproduce conflicts and ensure retries do not duplicate application work. See [API selection](api.md) and [troubleshooting](gotchas.md). diff --git a/external/develop/devops/cloudflare/references/r2-sql/README.md b/external/develop/devops/cloudflare/references/r2-sql/README.md deleted file mode 100644 index d02600d..0000000 --- a/external/develop/devops/cloudflare/references/r2-sql/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# Cloudflare R2 SQL - -Serverless, distributed, **read-only** query engine (Apache DataFusion) for Apache Iceberg tables in R2 Data Catalog. - -## Documentation - -For full function lists, data types, and pricing, **retrieve the live docs** — use the Cloudflare MCP `docs` tool if available, otherwise `webfetch`. - -| Topic | URL | -|-------|-----| -| Overview / get started | `https://developers.cloudflare.com/r2-sql/get-started/` | -| Query data | `https://developers.cloudflare.com/r2-sql/query-data/` | -| SQL reference | `https://developers.cloudflare.com/r2-sql/sql-reference/` | -| Aggregate functions | `https://developers.cloudflare.com/r2-sql/sql-reference/aggregate-functions/` | -| Scalar functions | `https://developers.cloudflare.com/r2-sql/sql-reference/scalar-functions/` | -| Complex types | `https://developers.cloudflare.com/r2-sql/sql-reference/complex-types/` | -| Limitations & best practices | `https://developers.cloudflare.com/r2-sql/reference/limitations-best-practices/` | -| Wrangler commands | `https://developers.cloudflare.com/r2-sql/reference/wrangler-commands/` | -| Pricing | `https://developers.cloudflare.com/r2-sql/platform/pricing/` | - -## Connection Values - -| Value | Format | -|-------|--------| -| REST endpoint | `https://api.sql.cloudflarestorage.com/api/v1/accounts/{ACCOUNT_ID}/r2-sql/query/{BUCKET}` | -| Wrangler | `npx wrangler r2 sql query "{WAREHOUSE}" ""` with `WRANGLER_R2_SQL_AUTH_TOKEN` set | -| Warehouse | `{ACCOUNT_ID}_{BUCKET}` | - -> The REST endpoint is `api.sql.cloudflarestorage.com` — **not** `api.cloudflare.com/.../r2/sql`. - -## Quick Start - -```bash -npx wrangler r2 bucket catalog enable my-bucket # 1. enable catalog -export WRANGLER_R2_SQL_AUTH_TOKEN= # 2. auth (Admin R&W + R2 SQL Read) -npx wrangler r2 sql query "$ACCOUNT_ID"_my-bucket \ - "SELECT * FROM default.my_table LIMIT 10" # 3. query -``` - -## SQL Surface - -R2 SQL is read-only and supports a broad analytical SQL surface (SELECT, JOINs, subqueries, CTEs, set operations, window functions, and aggregate/scalar/JSON functions over complex types). For the authoritative, current list of supported syntax, functions, and limitations, see the SQL reference and limitations docs linked above. [api.md](api.md) has query templates. - -## When to Use - -**Use for:** SQL analytics over Iceberg (logs, BI, fraud, ad-hoc), multi-cloud queries without egress, dashboards (query from a Worker via HTTP). - -**Don't use for:** writes (use PySpark/PyIceberg) or real-time OLTP (<100 ms). - -## No Workers Binding - -There is no `env.R2_SQL` binding. Query from a Worker via `fetch()` to the REST endpoint with the token as a secret (see [patterns.md](patterns.md#dashboard-worker)). - -## Reading Order - -1. [configuration.md](configuration.md) — enable catalog, tokens, env setup -2. [api.md](api.md) — SQL syntax templates, JOIN/window examples, response format, data types -3. [patterns.md](patterns.md) — CLI/REST/Worker queries, use cases, pagination, performance -4. [gotchas.md](gotchas.md) — what works vs. not, performance, troubleshooting - -## See Also - -- [r2-data-catalog](../r2-data-catalog/) — PyIceberg/PySpark, table management -- [pipelines](../pipelines/) — streaming ingest into queryable tables diff --git a/external/develop/devops/cloudflare/references/r2-sql/api.md b/external/develop/devops/cloudflare/references/r2-sql/api.md deleted file mode 100644 index 3411613..0000000 --- a/external/develop/devops/cloudflare/references/r2-sql/api.md +++ /dev/null @@ -1,121 +0,0 @@ -# R2 SQL API Reference - -Read-only SQL over Iceberg (Apache DataFusion). Query templates only. For the authoritative list of supported syntax, functions, data types, and limitations, pull the SQL reference (`sql-reference/`, `.../aggregate-functions/`, `.../scalar-functions/`, `.../complex-types/`) and `reference/limitations-best-practices/`. - -## Query Endpoint - -``` -POST https://api.sql.cloudflarestorage.com/api/v1/accounts/{ACCOUNT_ID}/r2-sql/query/{BUCKET} -Authorization: Bearer -Content-Type: application/json -Body: {"query": ""} -``` - -CLI: `npx wrangler r2 sql query "{WAREHOUSE}" ""` (with `WRANGLER_R2_SQL_AUTH_TOKEN`). - -## Response Format - -```json -{ - "result": { - "request_id": "dqe-prod-01...", - "schema": [{"name": "cnt", "descriptor": {"type": {"name": "int64"}, "nullable": false}}], - "rows": [{"category": "Electronics", "cnt": 12345}], - "metrics": {"r2_requests_count": 5, "files_scanned": 29, "bytes_scanned": 12345678, "cache_hits": 0} - }, - "success": true, "errors": [] -} -``` - -Error: `{"result": null, "success": false, "errors": [{"code": 40003, "message": "..."}]}`. `bytes_scanned` ≈ billable data. - -## Query Structure - -```sql -SELECT [DISTINCT] columns | expressions | aggregations -FROM namespace.table [alias] -[ [INNER|LEFT|RIGHT|FULL OUTER|CROSS] JOIN namespace.table2 alias2 ON ... ] -[WHERE ...] [GROUP BY ...] [HAVING ...] -[QUALIFY window_predicate] -[ORDER BY expr [ASC|DESC]] -[LIMIT n] -- default 500, max 10,000 -``` - -## Schema Discovery - -```sql -SHOW DATABASES; -- list namespaces (aliases: SHOW NAMESPACES / SHOW SCHEMAS) -SHOW TABLES IN namespace; -DESCRIBE namespace.table; -- columns, types, partition keys -EXPLAIN [FORMAT JSON] SELECT ...; -- execution plan (free; no data scanned) -``` - -## JOINs / Subqueries / CTEs / Set Ops - -```sql --- JOINs: all types + multi-way -SELECT z.domain, COUNT(*) AS cnt -FROM ns.zones z -INNER JOIN ns.http_requests h ON z.zone_id = h.zone_id -LEFT JOIN ns.firewall_events f ON z.zone_id = f.zone_id -GROUP BY z.domain ORDER BY cnt DESC LIMIT 20; - --- Subqueries: IN / EXISTS / scalar / derived -SELECT * FROM ns.t1 WHERE id IN (SELECT id FROM ns.t2 WHERE x > 0); -SELECT col, (SELECT COUNT(*) FROM ns.t2 s WHERE s.id = t.id) AS cnt FROM ns.t1 t; - --- Multi-table CTE with JOIN -WITH top AS (SELECT zone_id, COUNT(*) AS req FROM ns.http_requests GROUP BY zone_id ORDER BY req DESC LIMIT 50) -SELECT t.zone_id, t.req FROM top t LEFT JOIN ns.zones z ON t.zone_id = z.zone_id; - --- Set ops: UNION / UNION ALL / INTERSECT / EXCEPT -SELECT zone_id FROM ns.firewall_events WHERE action = 'block' -UNION SELECT zone_id FROM ns.http_requests WHERE risk_score > 0.8; -``` - -## Window Functions - -Use inline `OVER (...)`. See the SQL reference for the full list of supported window functions and frame syntax. - -```sql -SELECT event_id, - ROW_NUMBER() OVER (PARTITION BY mag_type ORDER BY magnitude DESC) AS rn, - LAG(magnitude, 2, 0.0) OVER (ORDER BY occurred_at) AS prev2, -- offset + default - NTH_VALUE(magnitude, 2) OVER (ORDER BY magnitude DESC) AS n2, - SUM(magnitude) OVER (ORDER BY occurred_at) AS running, - AVG(magnitude) OVER (ORDER BY magnitude ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg -FROM ns.earthquakes; - --- QUALIFY: filter on a window result (top row per partition) -SELECT event_id, mag_type, magnitude FROM ns.earthquakes -QUALIFY ROW_NUMBER() OVER (PARTITION BY mag_type ORDER BY magnitude DESC) = 1; -``` - -## Functions - -Aggregate, scalar, JSON, and array/map function catalogs are in the docs — pull `sql-reference/aggregate-functions/` and `.../scalar-functions/`. JSON functions accept variadic paths, e.g. `json_get_int(doc, 'user', 'profile', 'level')`. - -## Data Types - -`integer`, `float`, `string` (single quotes), `boolean`, `timestamp` (RFC3339 **with timezone**), `date` (ISO 8601), `struct`, `array` (1-indexed), `map`. No implicit conversions — quote strings, include timezone on timestamps, don't quote integers. Full type docs: `sql-reference/`. - -```sql -WHERE status = 200 AND method = 'GET' -- not '200', not GET - AND ts >= '2026-01-01T00:00:00Z' -- not '2026-01-01' -``` - -## Complex Types (quick examples; full ref in docs) - -```sql -SELECT pricing['price'] AS price, get_field(pricing, 'discount') AS disc FROM ns.t; -- struct -SELECT tags[1] AS first_tag, array_length(tags) AS n FROM ns.t; -- array (1-indexed) -SELECT map_keys(meta), map_extract(meta, 'source') FROM ns.t; -- map -``` - -## Errors - -Failed queries return `{"success": false, "errors": [{"code": ..., "message": ...}]}`. For error codes and troubleshooting, see `https://developers.cloudflare.com/r2-sql/troubleshooting/`. - -## See Also - -- [patterns.md](patterns.md) — query examples · [gotchas.md](gotchas.md) — limits & workarounds · [configuration.md](configuration.md) diff --git a/external/develop/devops/cloudflare/references/r2-sql/configuration.md b/external/develop/devops/cloudflare/references/r2-sql/configuration.md deleted file mode 100644 index 4dbc049..0000000 --- a/external/develop/devops/cloudflare/references/r2-sql/configuration.md +++ /dev/null @@ -1,50 +0,0 @@ -# R2 SQL Configuration - -Auth and setup. For the current permission matrix and wrangler flags, pull `https://developers.cloudflare.com/r2-sql/reference/wrangler-commands/` and the R2 Data Catalog manage-catalogs doc. - -## Prerequisites - -- R2 bucket with Data Catalog enabled ([r2-data-catalog/configuration.md](../r2-data-catalog/configuration.md)) -- R2 API token: **R2 Storage Admin Read & Write** (includes R2 SQL Read), or add **R2 SQL Read** explicitly -- Wrangler CLI (for CLI queries) - -> Open-beta limitation: R2 Storage **Admin Read & Write is required even for read-only R2 SQL queries**. - -## Enable Catalog + Get Warehouse - -```bash -npx wrangler r2 bucket catalog enable my-bucket -``` - -You query by **warehouse** name (`{ACCOUNT_ID}_{BUCKET}`), shown in the output alongside the Catalog URI. - -## Configure Auth - -### Wrangler CLI - -```bash -export WRANGLER_R2_SQL_AUTH_TOKEN= -# or a .env file in the project dir (auto-loaded): WRANGLER_R2_SQL_AUTH_TOKEN= -``` - -> Wrangler does **not** use the `wrangler login` OAuth session for R2 SQL — the env var is required. - -### REST API - -```bash -curl -X POST \ - "https://api.sql.cloudflarestorage.com/api/v1/accounts/$ACCOUNT_ID/r2-sql/query/$BUCKET" \ - -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ - -d '{"query": "SELECT * FROM default.my_table LIMIT 10"}' -``` - -## Verify Setup - -```bash -npx wrangler r2 sql query "${ACCOUNT_ID}_my-bucket" "SHOW DATABASES" -npx wrangler r2 sql query "${ACCOUNT_ID}_my-bucket" "SHOW TABLES IN default" -``` - -## See Also - -- [api.md](api.md) — SQL syntax · [patterns.md](patterns.md) — query examples · [gotchas.md](gotchas.md) — troubleshooting diff --git a/external/develop/devops/cloudflare/references/r2-sql/gotchas.md b/external/develop/devops/cloudflare/references/r2-sql/gotchas.md deleted file mode 100644 index b75824d..0000000 --- a/external/develop/devops/cloudflare/references/r2-sql/gotchas.md +++ /dev/null @@ -1,39 +0,0 @@ -# R2 SQL Gotchas - -Operational pitfalls. For the authoritative list of supported features, unsupported features, and recommended workarounds, pull `https://developers.cloudflare.com/r2-sql/reference/limitations-best-practices/` and `https://developers.cloudflare.com/r2-sql/troubleshooting/`. - -## Access - -- **No Workers binding.** There is no `env.R2_SQL`. Query the REST endpoint via `fetch()` from a Worker ([patterns.md](patterns.md#dashboard-worker)), or use D1 / an external DB for OLTP. -- Wrangler needs `WRANGLER_R2_SQL_AUTH_TOKEN` — it does **not** reuse the `wrangler login` OAuth session. -- Open beta: R2 Storage **Admin Read & Write is required even for read-only** queries. - -## Type Safety - -```sql --- ❌ wrong -- ✅ right -WHERE status = '200' WHERE status = 200 -WHERE ts > '2026-01-01' WHERE ts > '2026-01-01T00:00:00Z' -- need time + tz -WHERE method = GET WHERE method = 'GET' -``` - -No implicit conversions. Timestamps must be RFC3339 with timezone; dates ISO 8601. - -## Performance - -- **File count dominates latency** — enable automatic compaction. -- **Partition-filter + narrow time windows + always `LIMIT`.** -- **Multi-way JOINs on large tables** can exceed resource limits — filter heavily, join through dimension tables. -- Per-query `metrics` (`files_scanned`, `bytes_scanned`, `cache_hits`) are the primary observability signal; `bytes_scanned` ≈ billable data. For LIMIT bounds, pagination, and other guidance, see the limitations-best-practices doc. - -## Debug Checklist - -1. `wrangler r2 bucket catalog enable ` — catalog on? -2. `echo $WRANGLER_R2_SQL_AUTH_TOKEN` — token set? -3. `SHOW DATABASES` → `SHOW TABLES IN ns` → `DESCRIBE ns.table` -4. `SELECT COUNT(*) FROM ns.table` — data present? -5. Add filters incrementally; read `metrics` to tune. - -## See Also - -- [api.md](api.md) · [patterns.md](patterns.md) · [configuration.md](configuration.md) diff --git a/external/develop/devops/cloudflare/references/r2-sql/patterns.md b/external/develop/devops/cloudflare/references/r2-sql/patterns.md deleted file mode 100644 index 0359e9a..0000000 --- a/external/develop/devops/cloudflare/references/r2-sql/patterns.md +++ /dev/null @@ -1,118 +0,0 @@ -# R2 SQL Patterns - -Code templates for CLI, REST, and Worker access. For performance/partitioning best practices, pull `https://developers.cloudflare.com/r2-sql/reference/limitations-best-practices/`. - -## Wrangler CLI - -```bash -export WRANGLER_R2_SQL_AUTH_TOKEN=$API_TOKEN - -npx wrangler r2 sql query "${ACCOUNT_ID}_my-bucket" " - SELECT category, COUNT(*) AS cnt, round(AVG(amount), 2) AS avg_amount - FROM analytics.events - WHERE __ingest_ts >= '2026-01-01T00:00:00Z' - GROUP BY category ORDER BY cnt DESC LIMIT 100" -``` - -## REST API (Python) - -```python -import requests - -API = f"https://api.sql.cloudflarestorage.com/api/v1/accounts/{ACCOUNT_ID}/r2-sql/query/{BUCKET}" -HEADERS = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"} - -def r2sql(query): - body = requests.post(API, headers=HEADERS, json={"query": query}, timeout=180).json() - if body["success"]: - return body["result"]["rows"], body["result"]["metrics"] - raise RuntimeError(body["errors"]) - -rows, metrics = r2sql("SELECT category, COUNT(*) AS cnt FROM analytics.events GROUP BY category LIMIT 10") -``` - -## REST API (curl) - -```bash -curl -X POST \ - "https://api.sql.cloudflarestorage.com/api/v1/accounts/$ACCOUNT_ID/r2-sql/query/$BUCKET" \ - -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ - -d '{"query": "SELECT COUNT(*) AS total FROM analytics.events"}' -``` - -## Dashboard Worker - -No R2 SQL binding exists — query the REST endpoint via `fetch()`. - -```typescript -interface Env { ACCOUNT_ID: string; BUCKET: string; R2_SQL_TOKEN: string; } - -async function queryR2SQL(env: Env, query: string) { - const url = `https://api.sql.cloudflarestorage.com/api/v1/accounts/${env.ACCOUNT_ID}/r2-sql/query/${env.BUCKET}`; - const resp = await fetch(url, { - method: "POST", - headers: { Authorization: `Bearer ${env.R2_SQL_TOKEN}`, "Content-Type": "application/json" }, - body: JSON.stringify({ query }), - }); - if (!resp.ok) throw new Error(`R2 SQL ${resp.status}: ${await resp.text()}`); - return (await resp.json() as any).result; -} - -export default { - async fetch(req: Request, env: Env): Promise { - if (new URL(req.url).pathname === "/api/analytics") { - const result = await queryR2SQL(env, ` - SELECT category, COUNT(*) AS cnt FROM analytics.events - GROUP BY category ORDER BY cnt DESC LIMIT 10`); - return Response.json(result.rows); - } - return new Response("Not found", { status: 404 }); - }, -}; -``` - -```bash -npx wrangler secret put R2_SQL_TOKEN -``` - -## Example Queries - -```sql --- Error rate by endpoint -SELECT path, COUNT(*) AS total, SUM(CASE WHEN status >= 400 THEN 1 ELSE 0 END) AS errors -FROM logs.http_requests WHERE __ingest_ts >= '2026-01-01T00:00:00Z' -GROUP BY path ORDER BY errors DESC LIMIT 20; - --- Top-3 slowest requests per method (window + QUALIFY) -SELECT method, path, response_time_ms FROM logs.http_requests -QUALIFY ROW_NUMBER() OVER (PARTITION BY method ORDER BY response_time_ms DESC) <= 3; - --- Cross-table analytics with approx distinct -SELECT z.domain, COUNT(*) AS requests, approx_distinct(h.client_ip) AS uniques -FROM ns.zones z INNER JOIN ns.http_requests h ON z.zone_id = h.zone_id -WHERE h.__ingest_ts >= '2026-06-01T00:00:00Z' -GROUP BY z.domain ORDER BY requests DESC LIMIT 25; -``` - -## Cursor-Based Pagination - -Paginate on a sortable (ideally partition) column rather than `OFFSET`: - -```sql -SELECT * FROM logs.requests ORDER BY __ingest_ts DESC LIMIT 500; -- page 1 -SELECT * FROM logs.requests WHERE __ingest_ts < '' ORDER BY __ingest_ts DESC LIMIT 500; -- page 2 -``` - -## Performance (essentials) - -- **Always `LIMIT`** (early termination); **filter on partition keys first** (`__ingest_ts` range), then add predicates. -- **Narrow time ranges**; **compact tables** (file count dominates latency — enable automatic compaction in [r2-data-catalog](../r2-data-catalog/configuration.md)). -- Read response `metrics` (`files_scanned`, `bytes_scanned`) to tune. Full guidance: limitations-best-practices doc. - -## Pipelines → R2 SQL - -After `npx wrangler pipelines setup` (Data Catalog destination), wait for first flush (3–7 min), then query the table. See [pipelines/patterns.md](../pipelines/patterns.md). - -## See Also - -- [api.md](api.md) · [gotchas.md](gotchas.md) · [r2-data-catalog/patterns.md](../r2-data-catalog/patterns.md) diff --git a/external/develop/devops/cloudflare/references/r2/README.md b/external/develop/devops/cloudflare/references/r2/README.md index df20801..8556511 100644 --- a/external/develop/devops/cloudflare/references/r2/README.md +++ b/external/develop/devops/cloudflare/references/r2/README.md @@ -4,8 +4,8 @@ Use R2 for objects such as uploads, media, backups, and static assets. Fetch the ## Choose an access path -- Use a Workers binding for object access inside a Worker: [Workers API setup](https://developers.cloudflare.com/r2/get-started/workers-api/). -- Use the S3-compatible API for existing S3 clients or direct client access through presigned URLs: [S3 setup](https://developers.cloudflare.com/r2/get-started/s3/). Check supported operations rather than assuming full S3 parity. +- Use a Workers binding for object access inside a Worker: [Workers API setup](https://developers.cloudflare.com/r2/get-started/workers-api/index.md). +- Use the S3-compatible API for existing S3 clients or direct client access through presigned URLs: [S3 setup](https://developers.cloudflare.com/r2/get-started/s3/index.md). Check supported operations rather than assuming full S3 parity. - Decide whether objects need application authorization, temporary access, or public delivery before exposing the bucket. See [patterns.md](./patterns.md). ## Find the task @@ -17,10 +17,10 @@ Use R2 for objects such as uploads, media, backups, and static assets. Fetch the | Uploads, streaming, caching, public delivery, event processing | [patterns.md](./patterns.md) | | Pagination, conditional responses, failed uploads, limits | [gotchas.md](./gotchas.md) | -For other topics, discover pages through the [R2 documentation index](https://developers.cloudflare.com/r2/llms.txt). Check [pricing](https://developers.cloudflare.com/r2/pricing/) before estimating costs. +For other topics, discover pages through the [R2 documentation index](https://developers.cloudflare.com/r2/llms.txt). Check [pricing](https://developers.cloudflare.com/r2/pricing/index.md) before estimating costs. ## See also -- [Workers](https://developers.cloudflare.com/workers/) for request handling. +- [Workers](https://developers.cloudflare.com/workers/index.md) for request handling. - [KV](../kv/) or [D1](../d1/) for application metadata associated with objects. - [Queues](../queues/) for asynchronous processing of object events. diff --git a/external/develop/devops/cloudflare/references/r2/api.md b/external/develop/devops/cloudflare/references/r2/api.md index af0e15a..63e4a5d 100644 --- a/external/develop/devops/cloudflare/references/r2/api.md +++ b/external/develop/devops/cloudflare/references/r2/api.md @@ -4,13 +4,13 @@ Fetch the relevant page before writing code. Use the Workers API for bucket bind | Task | Current documentation | |------|-----------------------| -| Read, write, inspect, delete, or list objects; metadata, checksums, ranges, and return types | [Workers API reference](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/) | -| Implement a Worker that serves or writes objects | [Use R2 from Workers](https://developers.cloudflare.com/r2/api/workers/workers-api-usage/) | -| Create, resume, complete, or abort multipart uploads | [Multipart Worker and client example](https://developers.cloudflare.com/r2/api/workers/workers-multipart-usage/) | -| Check supported S3 operations and headers | [S3 compatibility](https://developers.cloudflare.com/r2/api/s3/api/) | -| Configure an S3 JavaScript client | [AWS SDK for JavaScript v3](https://developers.cloudflare.com/r2/examples/aws/aws-sdk-js-v3/) | -| Sign temporary upload or download access | [Presigned URLs](https://developers.cloudflare.com/r2/api/s3/presigned-urls/) | -| Encrypt with customer-provided keys | [SSE-C usage](https://developers.cloudflare.com/r2/examples/ssec/) | -| Manage buckets and objects from the command line | [Wrangler R2 commands](https://developers.cloudflare.com/r2/reference/wrangler-commands/) | +| Read, write, inspect, delete, or list objects; metadata, checksums, ranges, and return types | [Workers API reference](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/index.md) | +| Implement a Worker that serves or writes objects | [Use R2 from Workers](https://developers.cloudflare.com/r2/api/workers/workers-api-usage/index.md) | +| Create, resume, complete, or abort multipart uploads | [Multipart Worker and client example](https://developers.cloudflare.com/r2/api/workers/workers-multipart-usage/index.md) | +| Check supported S3 operations and headers | [S3 compatibility](https://developers.cloudflare.com/r2/api/s3/api/index.md) | +| Configure an S3 JavaScript client | [AWS SDK for JavaScript v3](https://developers.cloudflare.com/r2/examples/aws/aws-sdk-js-v3/index.md) | +| Sign temporary upload or download access | [Presigned URLs](https://developers.cloudflare.com/r2/api/s3/presigned-urls/index.md) | +| Encrypt with customer-provided keys | [SSE-C usage](https://developers.cloudflare.com/r2/examples/ssec/index.md) | +| Manage buckets and objects from the command line | [Wrangler R2 commands](https://developers.cloudflare.com/r2/reference/wrangler-commands/index.md) | -Use generated project types rather than maintaining local copies of R2 interfaces; see [Workers TypeScript guidance](https://developers.cloudflare.com/workers/languages/typescript/). For pagination and conditional response handling, read [gotchas.md](./gotchas.md) alongside the API reference. +Use generated project types rather than maintaining local copies of R2 interfaces; see [Workers TypeScript guidance](https://developers.cloudflare.com/workers/languages/typescript/index.md). For pagination and conditional response handling, read [gotchas.md](./gotchas.md) alongside the API reference. diff --git a/external/develop/devops/cloudflare/references/r2/configuration.md b/external/develop/devops/cloudflare/references/r2/configuration.md index 3e8d8a3..d64079f 100644 --- a/external/develop/devops/cloudflare/references/r2/configuration.md +++ b/external/develop/devops/cloudflare/references/r2/configuration.md @@ -4,16 +4,16 @@ Fetch the task's documentation before editing Wrangler configuration or bucket s | Task | Current documentation | |------|-----------------------| -| Create a bucket and bind it to a Worker | [Workers API setup](https://developers.cloudflare.com/r2/get-started/workers-api/) | -| Choose local simulation or a remote bucket during development | [Supported bindings per development mode](https://developers.cloudflare.com/workers/local-development/bindings-per-env/) and [local development](https://developers.cloudflare.com/workers/local-development/) | -| Create S3 credentials and scope permissions | [R2 authentication](https://developers.cloudflare.com/r2/api/tokens/) | -| Set the S3 endpoint and SDK region | [AWS SDK for JavaScript v3](https://developers.cloudflare.com/r2/examples/aws/aws-sdk-js-v3/) | -| Choose placement hints or a jurisdiction | [Data location](https://developers.cloudflare.com/r2/reference/data-location/) | -| Configure browser origins, methods, and headers | [CORS](https://developers.cloudflare.com/r2/buckets/cors/) | -| Set expiration, storage transitions, or incomplete-upload cleanup | [Object lifecycles](https://developers.cloudflare.com/r2/buckets/object-lifecycles/) | -| Choose or change storage classes | [Storage classes](https://developers.cloudflare.com/r2/buckets/storage-classes/) and [pricing](https://developers.cloudflare.com/r2/pricing/) | -| Send object events to a queue | [Event notifications](https://developers.cloudflare.com/r2/buckets/event-notifications/) | -| Configure public access or a custom domain | [Public buckets](https://developers.cloudflare.com/r2/buckets/public-buckets/) | -| Manage bucket settings with Wrangler | [R2 commands](https://developers.cloudflare.com/r2/reference/wrangler-commands/) | +| Create a bucket and bind it to a Worker | [Workers API setup](https://developers.cloudflare.com/r2/get-started/workers-api/index.md) | +| Choose local simulation or a remote bucket during development | [Supported bindings per development mode](https://developers.cloudflare.com/workers/local-development/bindings-per-env/index.md) and [local development](https://developers.cloudflare.com/workers/local-development/index.md) | +| Create S3 credentials and scope permissions | [R2 authentication](https://developers.cloudflare.com/r2/api/tokens/index.md) | +| Set the S3 endpoint and SDK region | [AWS SDK for JavaScript v3](https://developers.cloudflare.com/r2/examples/aws/aws-sdk-js-v3/index.md) | +| Choose placement hints or a jurisdiction | [Data location](https://developers.cloudflare.com/r2/reference/data-location/index.md) | +| Configure browser origins, methods, and headers | [CORS](https://developers.cloudflare.com/r2/buckets/cors/index.md) | +| Set expiration, storage transitions, or incomplete-upload cleanup | [Object lifecycles](https://developers.cloudflare.com/r2/buckets/object-lifecycles/index.md) | +| Choose or change storage classes | [Storage classes](https://developers.cloudflare.com/r2/buckets/storage-classes/index.md) and [pricing](https://developers.cloudflare.com/r2/pricing/index.md) | +| Send object events to a queue | [Event notifications](https://developers.cloudflare.com/r2/buckets/event-notifications/index.md) | +| Configure public access or a custom domain | [Public buckets](https://developers.cloudflare.com/r2/buckets/public-buckets/index.md) | +| Manage bucket settings with Wrangler | [R2 commands](https://developers.cloudflare.com/r2/reference/wrangler-commands/index.md) | Choose the development bucket deliberately: a remote binding accesses real data. Scope S3 credentials to the required buckets and operations; Workers bindings use their own access mechanism. Review lifecycle prefixes and retention needs before applying deletion rules, and evaluate retrieval and minimum-storage charges before choosing a storage class. diff --git a/external/develop/devops/cloudflare/references/r2/gotchas.md b/external/develop/devops/cloudflare/references/r2/gotchas.md index c6f2119..4a9a581 100644 --- a/external/develop/devops/cloudflare/references/r2/gotchas.md +++ b/external/develop/devops/cloudflare/references/r2/gotchas.md @@ -4,14 +4,14 @@ Use the current references to diagnose the actual response or error instead of c | Symptom or decision | What to check | |---------------------|---------------| -| Listing stops early or escapes the intended prefix | Follow `truncated` and the returned cursor, retaining the original prefix, delimiter, and metadata options on subsequent requests. See the listing section of the [Workers API reference](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/). | -| Conditional read has no body, or conditional write returns null | Distinguish a missing object from a failed condition; choose the HTTP response for the actual request condition. See conditional operations in the [Workers API reference](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/). | -| ETag, metadata, checksum, or stream upload behaves unexpectedly | Check supported values and return types in the [Workers API reference](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/), the [Worker upload example](https://developers.cloudflare.com/r2/api/workers/workers-api-usage/), and [Workers streams](https://developers.cloudflare.com/workers/runtime-apis/streams/). | -| Multipart upload fails or cannot be resumed | Check part constraints and handle an upload that has already completed or aborted: [multipart guide](https://developers.cloudflare.com/r2/api/workers/workers-multipart-usage/) and [API reference](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/). | -| S3 authentication or signed URL fails | Verify credentials, endpoint, region, operation, signed headers, and expiry using [SDK setup](https://developers.cloudflare.com/r2/examples/aws/aws-sdk-js-v3/), [authentication](https://developers.cloudflare.com/r2/api/tokens/), and [presigned URLs](https://developers.cloudflare.com/r2/api/s3/presigned-urls/). | -| Browser fails but an HTTP client succeeds | Check [CORS](https://developers.cloudflare.com/r2/buckets/cors/) and [troubleshooting](https://developers.cloudflare.com/r2/platform/troubleshooting/). | -| Local and deployed data or behavior differ | Check [local development](https://developers.cloudflare.com/workers/local-development/), [supported bindings](https://developers.cloudflare.com/workers/local-development/bindings-per-env/), and the local persistence options in [Wrangler R2 commands](https://developers.cloudflare.com/r2/reference/wrangler-commands/). | -| Reads serve old or missing content after an update | Check the [consistency model and cache interactions](https://developers.cloudflare.com/r2/reference/consistency/). | -| Upload size, metadata size, storage cost, or lifecycle behavior is unexpected | Fetch [limits](https://developers.cloudflare.com/r2/platform/limits/), [pricing](https://developers.cloudflare.com/r2/pricing/), [storage classes](https://developers.cloudflare.com/r2/buckets/storage-classes/), and [object lifecycles](https://developers.cloudflare.com/r2/buckets/object-lifecycles/). | +| Listing stops early or escapes the intended prefix | Follow `truncated` and the returned cursor, retaining the original prefix, delimiter, and metadata options on subsequent requests. See the listing section of the [Workers API reference](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/index.md). | +| Conditional read has no body, or conditional write returns null | Distinguish a missing object from a failed condition; choose the HTTP response for the actual request condition. See conditional operations in the [Workers API reference](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/index.md). | +| ETag, metadata, checksum, or stream upload behaves unexpectedly | Check supported values and return types in the [Workers API reference](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/index.md), the [Worker upload example](https://developers.cloudflare.com/r2/api/workers/workers-api-usage/index.md), and [Workers streams](https://developers.cloudflare.com/workers/runtime-apis/streams/index.md). | +| Multipart upload fails or cannot be resumed | Check part constraints and handle an upload that has already completed or aborted: [multipart guide](https://developers.cloudflare.com/r2/api/workers/workers-multipart-usage/index.md) and [API reference](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/index.md). | +| S3 authentication or signed URL fails | Verify credentials, endpoint, region, operation, signed headers, and expiry using [SDK setup](https://developers.cloudflare.com/r2/examples/aws/aws-sdk-js-v3/index.md), [authentication](https://developers.cloudflare.com/r2/api/tokens/index.md), and [presigned URLs](https://developers.cloudflare.com/r2/api/s3/presigned-urls/index.md). | +| Browser fails but an HTTP client succeeds | Check [CORS](https://developers.cloudflare.com/r2/buckets/cors/index.md) and [troubleshooting](https://developers.cloudflare.com/r2/platform/troubleshooting/index.md). | +| Local and deployed data or behavior differ | Check [local development](https://developers.cloudflare.com/workers/local-development/index.md), [supported bindings](https://developers.cloudflare.com/workers/local-development/bindings-per-env/index.md), and the local persistence options in [Wrangler R2 commands](https://developers.cloudflare.com/r2/reference/wrangler-commands/index.md). | +| Reads serve old or missing content after an update | Check the [consistency model and cache interactions](https://developers.cloudflare.com/r2/reference/consistency/index.md). | +| Upload size, metadata size, storage cost, or lifecycle behavior is unexpected | Fetch [limits](https://developers.cloudflare.com/r2/platform/limits/index.md), [pricing](https://developers.cloudflare.com/r2/pricing/index.md), [storage classes](https://developers.cloudflare.com/r2/buckets/storage-classes/index.md), and [object lifecycles](https://developers.cloudflare.com/r2/buckets/object-lifecycles/index.md). | -For other failures, start with [R2 troubleshooting](https://developers.cloudflare.com/r2/platform/troubleshooting/) and [error codes](https://developers.cloudflare.com/r2/api/error-codes/). +For other failures, start with [R2 troubleshooting](https://developers.cloudflare.com/r2/platform/troubleshooting/index.md) and [error codes](https://developers.cloudflare.com/r2/api/error-codes/index.md). diff --git a/external/develop/devops/cloudflare/references/r2/patterns.md b/external/develop/devops/cloudflare/references/r2/patterns.md index aeaf8dd..ebbded6 100644 --- a/external/develop/devops/cloudflare/references/r2/patterns.md +++ b/external/develop/devops/cloudflare/references/r2/patterns.md @@ -4,15 +4,15 @@ Choose the access and delivery model, then fetch the implementation guide. | Task | Current documentation | |------|-----------------------| -| Stream object downloads or accept uploads through a Worker | [Use R2 from Workers](https://developers.cloudflare.com/r2/api/workers/workers-api-usage/) | -| Add conditional reads/writes, range handling, checksums, or batch deletion | [Workers API reference](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/) | -| Upload large files with multipart state tracked by the client | [Multipart Worker and client example](https://developers.cloudflare.com/r2/api/workers/workers-multipart-usage/) | -| Upload directly from a browser or share a temporary download | [Presigned URLs](https://developers.cloudflare.com/r2/api/s3/presigned-urls/) and [CORS](https://developers.cloudflare.com/r2/buckets/cors/) | -| Cache responses served by a Worker | [Cache API example](https://developers.cloudflare.com/r2/examples/cache-api/) | -| Deliver public objects through a custom domain or evaluate r2.dev | [Public buckets](https://developers.cloudflare.com/r2/buckets/public-buckets/) | -| Process object changes asynchronously | [Event notifications](https://developers.cloudflare.com/r2/buckets/event-notifications/) and [Queues](../queues/) | -| Expire objects or transition storage classes | [Object lifecycles](https://developers.cloudflare.com/r2/buckets/object-lifecycles/) and [storage classes](https://developers.cloudflare.com/r2/buckets/storage-classes/) | +| Stream object downloads or accept uploads through a Worker | [Use R2 from Workers](https://developers.cloudflare.com/r2/api/workers/workers-api-usage/index.md) | +| Add conditional reads/writes, range handling, checksums, or batch deletion | [Workers API reference](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/index.md) | +| Upload large files with multipart state tracked by the client | [Multipart Worker and client example](https://developers.cloudflare.com/r2/api/workers/workers-multipart-usage/index.md) | +| Upload directly from a browser or share a temporary download | [Presigned URLs](https://developers.cloudflare.com/r2/api/s3/presigned-urls/index.md) and [CORS](https://developers.cloudflare.com/r2/buckets/cors/index.md) | +| Cache responses served by a Worker | [Cache API example](https://developers.cloudflare.com/r2/examples/cache-api/index.md) | +| Deliver public objects through a custom domain or evaluate r2.dev | [Public buckets](https://developers.cloudflare.com/r2/buckets/public-buckets/index.md) | +| Process object changes asynchronously | [Event notifications](https://developers.cloudflare.com/r2/buckets/event-notifications/index.md) and [Queues](../queues/) | +| Expire objects or transition storage classes | [Object lifecycles](https://developers.cloudflare.com/r2/buckets/object-lifecycles/index.md) and [storage classes](https://developers.cloudflare.com/r2/buckets/storage-classes/index.md) | Authorize the caller for the selected object key and operation before exposing a Worker endpoint or issuing a presigned URL. A key-format check alone does not establish access rights. Set the intended expiry and signed request constraints for temporary access; configure browser CORS separately. -Keep private responses out of shared public caches. Choose cache keys and invalidation around the application's access model, and check [R2 consistency and caching behavior](https://developers.cloudflare.com/r2/reference/consistency/) when objects can change. For multipart uploads, plan for failed parts, completion, and cleanup using the linked guide. +Keep private responses out of shared public caches. Choose cache keys and invalidation around the application's access model, and check [R2 consistency and caching behavior](https://developers.cloudflare.com/r2/reference/consistency/index.md) when objects can change. For multipart uploads, plan for failed parts, completion, and cleanup using the linked guide. diff --git a/external/develop/devops/cloudflare/references/realtime-sfu/README.md b/external/develop/devops/cloudflare/references/realtime-sfu/README.md deleted file mode 100644 index bb2187e..0000000 --- a/external/develop/devops/cloudflare/references/realtime-sfu/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# Cloudflare Realtime SFU Reference - -Expert guidance for building real-time audio/video/data applications using Cloudflare Realtime SFU (Selective Forwarding Unit). - -## Reading Order - -| Task | Files | ~Tokens | -|------|-------|---------| -| New project | README → configuration | ~1200 | -| Implement publish/subscribe | README → api | ~1600 | -| Add PartyTracks | patterns (PartyTracks section) | ~800 | -| Build presence system | patterns (DO section) | ~800 | -| Debug connection issues | gotchas | ~700 | -| Scale to millions | patterns (Cascading section) | ~600 | -| Add simulcast | patterns (Advanced section) | ~500 | -| Configure TURN | configuration (TURN section) | ~400 | - -## In This Reference - -- **[configuration.md](configuration.md)** - Setup, deployment, environment variables, Wrangler config -- **[api.md](api.md)** - Sessions, tracks, endpoints, request/response patterns -- **[patterns.md](patterns.md)** - Architecture patterns, use cases, integration examples -- **[gotchas.md](gotchas.md)** - Common issues, debugging, performance, security - -## Quick Start - -Cloudflare Realtime SFU: WebRTC infrastructure on global network (310+ cities). Anycast routing, no regional constraints, pub/sub model. - -**Core concepts:** -- **Sessions:** WebRTC PeerConnection to Cloudflare edge -- **Tracks:** Audio/video/data channels you publish or subscribe to -- **No rooms:** Build presence layer yourself via track sharing (see patterns.md) - -**Mental model:** Your client establishes one WebRTC session, publishes tracks (audio/video), shares track IDs via your backend, others subscribe to your tracks using track IDs + your session ID. - -## Choose Your Approach - -| Approach | When to Use | Complexity | -|----------|-------------|------------| -| **PartyTracks** | Production apps with device switching, React | Low - Observable-based, handles reconnections | -| **Raw API** | Custom requirements, non-browser, learning | Medium - Full control, manual WebRTC lifecycle | -| **RealtimeKit** | End-to-end SDK with UI components | Lowest - Managed state, React hooks | - -**Recommendation:** Start with PartyTracks for most production applications. See patterns.md for PartyTracks examples. - -## SFU vs RealtimeKit - -- **Realtime SFU:** WebRTC infrastructure (this reference). Build your own signaling, presence, UI. -- **RealtimeKit:** SDK layer on top of SFU. Includes React hooks, state management, UI components. Part of Cloudflare AI platform. - -Use SFU directly when you need custom signaling or non-React framework. Use RealtimeKit for faster development with React. - -## Setup - -Dashboard: https://dash.cloudflare.com/?to=/:account/calls - -Get `CALLS_APP_ID` and `CALLS_APP_SECRET` from dashboard, then see configuration.md for deployment. - -## See Also - -- [Orange Meets Demo](https://demo.orange.cloudflare.dev/) -- [Orange Source](https://github.com/cloudflare/orange) -- [Calls Examples](https://github.com/cloudflare/calls-examples) -- [API Reference](https://developers.cloudflare.com/api/resources/calls/) -- [RealtimeKit Docs](https://developers.cloudflare.com/realtime/realtimekit/) diff --git a/external/develop/devops/cloudflare/references/realtime-sfu/api.md b/external/develop/devops/cloudflare/references/realtime-sfu/api.md deleted file mode 100644 index 6e6dae6..0000000 --- a/external/develop/devops/cloudflare/references/realtime-sfu/api.md +++ /dev/null @@ -1,158 +0,0 @@ -# API Reference - -## Authentication - -```bash -curl -X POST 'https://rtc.live/v1/apps/${CALLS_APP_ID}/sessions/new' \ - -H "Authorization: Bearer ${CALLS_APP_SECRET}" -``` - -## Core Concepts - -**Sessions:** PeerConnection to Cloudflare edge -**Tracks:** Media/data channels (audio/video/datachannel) -**No rooms:** Build presence via track sharing - -## Client Libraries - -**PartyTracks (Recommended):** Observable-based client library for production use. Handles device changes, network switches, ICE restarts automatically. Push/pull API with React hooks. See patterns.md for full examples. - -```bash -npm install partytracks @cloudflare/calls -``` - -**Raw API:** Direct HTTP + WebRTC for custom requirements (documented below). - -## Endpoints - -### Create Session -```http -POST /v1/apps/{appId}/sessions/new -→ {sessionId, sessionDescription} -``` - -### Add Track (Publish) -```http -POST /v1/apps/{appId}/sessions/{sessionId}/tracks/new -Body: { - sessionDescription: {sdp, type: "offer"}, - tracks: [{location: "local", trackName: "my-video"}] -} -→ {sessionDescription, tracks: [{trackName}]} -``` - -### Add Track (Subscribe) -```http -POST /v1/apps/{appId}/sessions/{sessionId}/tracks/new -Body: { - tracks: [{ - location: "remote", - trackName: "remote-track-id", - sessionId: "other-session-id" - }] -} -→ {sessionDescription} (server offer) -``` - -### Renegotiate -```http -PUT /v1/apps/{appId}/sessions/{sessionId}/renegotiate -Body: {sessionDescription: {sdp, type: "answer"}} -``` - -### Close Tracks -```http -PUT /v1/apps/{appId}/sessions/{sessionId}/tracks/close -Body: {tracks: [{trackName}]} -→ {requiresImmediateRenegotiation: boolean} -``` - -### Get Session -```http -GET /v1/apps/{appId}/sessions/{sessionId} -→ {sessionId, tracks: TrackMetadata[]} -``` - -## TypeScript Types - -```typescript -interface TrackMetadata { - trackName: string; - location: "local" | "remote"; - sessionId?: string; // For remote tracks - mid?: string; // WebRTC mid -} -``` - -## WebRTC Flow - -```typescript -// 1. Create PeerConnection -const pc = new RTCPeerConnection({ - iceServers: [{urls: 'stun:stun.cloudflare.com:3478'}] -}); - -// 2. Add tracks -const stream = await navigator.mediaDevices.getUserMedia({video: true, audio: true}); -stream.getTracks().forEach(track => pc.addTrack(track, stream)); - -// 3. Create offer -const offer = await pc.createOffer(); -await pc.setLocalDescription(offer); - -// 4. Send to backend → Cloudflare API -const response = await fetch('/api/new-session', { - method: 'POST', - body: JSON.stringify({sdp: offer.sdp}) -}); - -// 5. Set remote answer -const {sessionDescription} = await response.json(); -await pc.setRemoteDescription(sessionDescription); -``` - -## Publishing - -```typescript -const offer = await pc.createOffer(); -await pc.setLocalDescription(offer); - -const res = await fetch(`/api/sessions/${sessionId}/tracks`, { - method: 'POST', - body: JSON.stringify({ - sdp: offer.sdp, - tracks: [{location: 'local', trackName: 'my-video'}] - }) -}); - -const {sessionDescription, tracks} = await res.json(); -await pc.setRemoteDescription(sessionDescription); -const publishedTrackId = tracks[0].trackName; // Share with others -``` - -## Subscribing - -```typescript -const res = await fetch(`/api/sessions/${sessionId}/tracks`, { - method: 'POST', - body: JSON.stringify({ - tracks: [{location: 'remote', trackName: remoteTrackId, sessionId: remoteSessionId}] - }) -}); - -const {sessionDescription} = await res.json(); -await pc.setRemoteDescription(sessionDescription); - -const answer = await pc.createAnswer(); -await pc.setLocalDescription(answer); - -await fetch(`/api/sessions/${sessionId}/renegotiate`, { - method: 'PUT', - body: JSON.stringify({sdp: answer.sdp}) -}); - -pc.ontrack = (event) => { - const [remoteStream] = event.streams; - videoElement.srcObject = remoteStream; -}; -``` diff --git a/external/develop/devops/cloudflare/references/realtime-sfu/configuration.md b/external/develop/devops/cloudflare/references/realtime-sfu/configuration.md deleted file mode 100644 index 6736b45..0000000 --- a/external/develop/devops/cloudflare/references/realtime-sfu/configuration.md +++ /dev/null @@ -1,137 +0,0 @@ -# Configuration & Deployment - -## Dashboard Setup - -1. Navigate to https://dash.cloudflare.com/?to=/:account/calls -2. Click "Create Application" (or use existing app) -3. Copy `CALLS_APP_ID` from dashboard -4. Generate and copy `CALLS_APP_SECRET` (treat as sensitive credential) -5. Use credentials in Wrangler config or environment variables below - -## Dependencies - -**Backend (Workers):** Built-in fetch API, no additional packages required - -**Client (PartyTracks):** -```bash -npm install partytracks @cloudflare/calls -``` - -**Client (React + PartyTracks):** -```bash -npm install partytracks @cloudflare/calls observable-hooks -# Observable hooks: useObservableAsValue, useValueAsObservable -``` - -**Client (Raw API):** Native browser WebRTC API only - -## Wrangler Setup - -```jsonc -{ - "name": "my-calls-app", - "main": "src/index.ts", - "compatibility_date": "2025-01-01", // Use current date for new projects - "vars": { - "CALLS_APP_ID": "your-app-id", - "MAX_WEBCAM_BITRATE": "1200000", - "MAX_WEBCAM_FRAMERATE": "24", - "MAX_WEBCAM_QUALITY_LEVEL": "1080" - }, - // Set secret: wrangler secret put CALLS_APP_SECRET - "durable_objects": { - "bindings": [ - { - "name": "ROOM", - "class_name": "Room" - } - ] - } -} -``` - -## Deploy - -```bash -wrangler login -wrangler secret put CALLS_APP_SECRET -wrangler deploy -``` - -## Environment Variables - -**Required:** -- `CALLS_APP_ID`: From dashboard -- `CALLS_APP_SECRET`: From dashboard (secret) - -**Optional:** -- `MAX_WEBCAM_BITRATE` (default: 1200000) -- `MAX_WEBCAM_FRAMERATE` (default: 24) -- `MAX_WEBCAM_QUALITY_LEVEL` (default: 1080) -- `TURN_SERVICE_ID`: TURN service -- `TURN_SERVICE_TOKEN`: TURN auth (secret) - -## TURN Configuration - -```javascript -const pc = new RTCPeerConnection({ - iceServers: [ - { urls: 'stun:stun.cloudflare.com:3478' }, - { - urls: [ - 'turn:turn.cloudflare.com:3478?transport=udp', - 'turn:turn.cloudflare.com:3478?transport=tcp', - 'turns:turn.cloudflare.com:5349?transport=tcp' - ], - username: turnUsername, - credential: turnCredential - } - ], - bundlePolicy: 'max-bundle', // Recommended: reduces overhead - iceTransportPolicy: 'all' // Use 'relay' to force TURN (testing only) -}); -``` - -**Ports:** 3478 (UDP/TCP), 53 (UDP), 80 (TCP), 443 (TLS), 5349 (TLS) - -**When to use TURN:** Required for restrictive corporate firewalls/networks that block UDP. ~5-10% of connections fallback to TURN. STUN works for most users. - -**ICE candidate filtering:** Cloudflare handles candidate filtering automatically. No need to manually filter candidates. - -## Durable Object Boilerplate - -Minimal presence system: - -```typescript -export class Room { - private sessions = new Map(); - - async fetch(req: Request) { - const {pathname} = new URL(req.url); - const body = await req.json(); - - if (pathname === '/join') { - this.sessions.set(body.sessionId, {userId: body.userId, tracks: []}); - return Response.json({participants: this.sessions.size}); - } - - if (pathname === '/publish') { - this.sessions.get(body.sessionId)?.tracks.push(...body.tracks); - // Broadcast to others via WebSocket (not shown) - return new Response('OK'); - } - - return new Response('Not found', {status: 404}); - } -} -``` - -## Environment Validation - -Check credentials before first API call: - -```typescript -if (!env.CALLS_APP_ID || !env.CALLS_APP_SECRET) { - throw new Error('CALLS_APP_ID and CALLS_APP_SECRET required'); -} -``` diff --git a/external/develop/devops/cloudflare/references/realtime-sfu/gotchas.md b/external/develop/devops/cloudflare/references/realtime-sfu/gotchas.md deleted file mode 100644 index efe5ee7..0000000 --- a/external/develop/devops/cloudflare/references/realtime-sfu/gotchas.md +++ /dev/null @@ -1,133 +0,0 @@ -# Gotchas & Troubleshooting - -## Common Errors - -### "Slow initial connect (~1.8s)" - -**Cause:** First STUN delayed during consensus forming (normal behavior) -**Solution:** Subsequent connections are faster. CF detects DTLS ClientHello early to compensate. - -### "No media flow" - -**Cause:** SDP exchange incomplete, connection not established, tracks not added before offer, browser permissions missing -**Solution:** -1. Verify SDP exchange complete -2. Check `pc.connectionState === 'connected'` -3. Ensure tracks added before creating offer -4. Confirm browser permissions granted -5. Use `chrome://webrtc-internals` for debugging - -### "Track not receiving" - -**Cause:** Track not published, track ID not shared, session IDs mismatch, `pc.ontrack` not set, renegotiation needed -**Solution:** -1. Verify track published successfully -2. Confirm track ID shared between peers -3. Check session IDs match -4. Set `pc.ontrack` handler before answer -5. Trigger renegotiation if needed - -### "ICE connection failed" - -**Cause:** Network changed, firewall blocked UDP, TURN needed, transient network issue -**Solution:** -```typescript -pc.oniceconnectionstatechange = async () => { - if (pc.iceConnectionState === 'failed') { - console.warn('ICE failed, attempting restart'); - await pc.restartIce(); // Triggers new ICE gathering - - // Create new offer with ICE restart flag - const offer = await pc.createOffer({iceRestart: true}); - await pc.setLocalDescription(offer); - - // Send to backend → Cloudflare API - await fetch(`/api/sessions/${sessionId}/renegotiate`, { - method: 'PUT', - body: JSON.stringify({sdp: offer.sdp}) - }); - } -}; -``` - -### "Track stuck/frozen" - -**Cause:** Sender paused track, network congestion, codec mismatch, mobile browser backgrounded -**Solution:** -1. Check `track.enabled` and `track.readyState === 'live'` -2. Verify sender active: `pc.getSenders().find(s => s.track === track)` -3. Check stats for packet loss/jitter (see patterns.md) -4. On mobile: Re-acquire tracks when app foregrounded -5. Test with different codecs if persistent - -### "Network change disconnects call" - -**Cause:** Mobile switching WiFi↔cellular, laptop changing networks -**Solution:** -```typescript -// Listen for network changes -if ('connection' in navigator) { - (navigator as any).connection.addEventListener('change', async () => { - console.log('Network changed'); - await pc.restartIce(); // Use ICE restart pattern above - }); -} - -// Or use PartyTracks (handles automatically) -``` - -## Retry with Exponential Backoff - -```typescript -async function fetchWithRetry(url: string, options: RequestInit, maxRetries = 3) { - for (let i = 0; i < maxRetries; i++) { - try { - const res = await fetch(url, options); - if (res.ok) return res; - if (res.status >= 500) throw new Error('Server error'); - return res; // Client error, don't retry - } catch (err) { - if (i === maxRetries - 1) throw err; - const delay = Math.min(1000 * 2 ** i, 10000); // Cap at 10s - await new Promise(resolve => setTimeout(resolve, delay)); - } - } -} -``` - -## Debugging with chrome://webrtc-internals - -1. Open `chrome://webrtc-internals` in Chrome/Edge -2. Find your PeerConnection in the list -3. Check **Stats graphs** for packet loss, jitter, bandwidth -4. Check **ICE candidate pairs**: Look for `succeeded` state, relay vs host candidates -5. Check **getStats**: Raw metrics for inbound/outbound RTP -6. Look for errors in **Event log**: `iceConnectionState`, `connectionState` changes -7. Export data with "Download the PeerConnection updates and stats data" button -8. Common issues visible here: ICE failures, high packet loss, bitrate drops - -## Limits - -| Resource/Limit | Value | Notes | -|----------------|-------|-------| -| Egress (Free) | 1TB/month | Per account | -| Egress (Paid) | $0.05/GB | After free tier | -| Inbound traffic | Free | All plans | -| TURN service | Free | Included with SFU | -| Participants | No hard limit | Client bandwidth/CPU bound (typically 10-50 tracks) | -| Tracks per session | No hard limit | Client resources limited | -| Session duration | No hard limit | Production calls run for hours | -| WebRTC ports | UDP 1024-65535 | Outbound only, required for media | -| API rate limit | 600 req/min | Per app, burst allowed | - -## Security Checklist - -- ✅ **Never expose** `CALLS_APP_SECRET` to client -- ✅ **Validate user identity** in backend before creating sessions -- ✅ **Implement auth tokens** for session access (JWT in custom header) -- ✅ **Rate limit** session creation endpoints -- ✅ **Expire sessions** server-side after inactivity -- ✅ **Validate track IDs** before subscribing (prevent unauthorized access) -- ✅ **Use HTTPS** for all signaling (API calls) -- ✅ **Enable DTLS-SRTP** (automatic with Cloudflare, encrypts media) -- ⚠️ **Consider E2EE** for sensitive content (implement client-side with Insertable Streams API) diff --git a/external/develop/devops/cloudflare/references/realtime-sfu/patterns.md b/external/develop/devops/cloudflare/references/realtime-sfu/patterns.md deleted file mode 100644 index 95ddc42..0000000 --- a/external/develop/devops/cloudflare/references/realtime-sfu/patterns.md +++ /dev/null @@ -1,174 +0,0 @@ -# Patterns & Use Cases - -## Architecture - -``` -Client (WebRTC) <---> CF Edge <---> Backend (HTTP) - | - CF Backbone (310+ DCs) - | - Other Edges <---> Other Clients -``` - -Anycast: Last-mile <50ms (95%), no region select, NACK shield, distributed consensus - -Cascading trees auto-scale to millions: -``` -Publisher -> Edge A -> Edge B -> Sub1 - \-> Edge C -> Sub2,3 -``` - -## Use Cases - -**1:1:** A creates session+publishes, B creates+subscribes to A+publishes, A subscribes to B -**N:N:** All create session+publish, backend broadcasts track IDs, all subscribe to others -**1:N:** Publisher creates+publishes, viewers each create+subscribe (no fan-out limit) -**Breakout:** Same PeerConnection! Backend closes/adds tracks, no recreation - -## PartyTracks (Recommended) - -Observable-based client with automatic device/network handling: - -```typescript -import {PartyTracks} from 'partytracks'; - -// Create client -const pt = new PartyTracks({ - apiUrl: '/api/calls', - sessionId: 'my-session', - onTrack: (track, peer) => { - const video = document.getElementById(`video-${peer.id}`) as HTMLVideoElement; - video.srcObject = new MediaStream([track]); - } -}); - -// Publish camera (push API) -const camera = await pt.getCamera(); // Auto-requests permissions, handles device changes -await pt.publishTrack(camera, {trackName: 'my-camera'}); - -// Subscribe to remote track (pull API) -await pt.subscribeToTrack({trackName: 'remote-camera', sessionId: 'other-session'}); - -// React hook example -import {useObservableAsValue} from 'observable-hooks'; - -function VideoCall() { - const localTracks = useObservableAsValue(pt.localTracks$); - const remoteTracks = useObservableAsValue(pt.remoteTracks$); - - return
{/* Render tracks */}
; -} - -// Screenshare -const screen = await pt.getScreenshare(); -await pt.publishTrack(screen, {trackName: 'my-screen'}); - -// Handle device changes (automatic) -// PartyTracks detects device changes (e.g., Bluetooth headset) and renegotiates -``` - -## Backend - -Express: -```js -app.post('/api/new-session', async (req, res) => { - const r = await fetch(`${CALLS_API}/apps/${process.env.CALLS_APP_ID}/sessions/new`, - {method: 'POST', headers: {'Authorization': `Bearer ${process.env.CALLS_APP_SECRET}`}}); - res.json(await r.json()); -}); -``` - -Workers: Same pattern, use `env.CALLS_APP_ID` and `env.CALLS_APP_SECRET` - -DO Presence: See configuration.md for boilerplate - -## Audio Level Detection - -```typescript -// Attach analyzer to audio track -function attachAudioLevelDetector(track: MediaStreamTrack) { - const ctx = new AudioContext(); - const analyzer = ctx.createAnalyser(); - const src = ctx.createMediaStreamSource(new MediaStream([track])); - src.connect(analyzer); - - const data = new Uint8Array(analyzer.frequencyBinCount); - const checkLevel = () => { - analyzer.getByteFrequencyData(data); - const level = data.reduce((a, b) => a + b) / data.length; - if (level > 30) console.log('Speaking:', level); // Trigger UI update - requestAnimationFrame(checkLevel); - }; - checkLevel(); -} -``` - -## Connection Quality Monitoring - -```typescript -pc.getStats().then(stats => { - stats.forEach(report => { - if (report.type === 'inbound-rtp' && report.kind === 'video') { - const {packetsLost, packetsReceived, jitter} = report; - const lossRate = packetsLost / (packetsLost + packetsReceived); - if (lossRate > 0.05) console.warn('High packet loss:', lossRate); - if (jitter > 100) console.warn('High jitter:', jitter); - } - }); -}); -``` - -## Stage Management (Limit Visible Participants) - -```typescript -// Subscribe to top 6 active speakers only -let activeSubscriptions = new Set(); - -function updateStage(topSpeakers: string[]) { - const toAdd = topSpeakers.filter(id => !activeSubscriptions.has(id)).slice(0, 6); - const toRemove = [...activeSubscriptions].filter(id => !topSpeakers.includes(id)); - - toRemove.forEach(id => { - pc.getSenders().find(s => s.track?.id === id)?.track?.stop(); - activeSubscriptions.delete(id); - }); - - toAdd.forEach(async id => { - await fetch(`/api/subscribe`, {method: 'POST', body: JSON.stringify({trackId: id})}); - activeSubscriptions.add(id); - }); -} -``` - -## Advanced - -Bandwidth mgmt: -```ts -const s = pc.getSenders().find(s => s.track?.kind === 'video'); -const p = s.getParameters(); -if (!p.encodings) p.encodings = [{}]; -p.encodings[0].maxBitrate = 1200000; p.encodings[0].maxFramerate = 24; -await s.setParameters(p); -``` - -Simulcast (CF auto-forwards best layer): -```ts -pc.addTransceiver('video', {direction: 'sendonly', sendEncodings: [ - {rid: 'high', maxBitrate: 1200000}, - {rid: 'med', maxBitrate: 600000, scaleResolutionDownBy: 2}, - {rid: 'low', maxBitrate: 200000, scaleResolutionDownBy: 4} -]}); -``` - -DataChannel: -```ts -const dc = pc.createDataChannel('chat', {ordered: true, maxRetransmits: 3}); -dc.onopen = () => dc.send(JSON.stringify({type: 'chat', text: 'Hi'})); -dc.onmessage = (e) => console.log('RX:', JSON.parse(e.data)); -``` - -**WHIP/WHEP:** For streaming interop (OBS → SFU, SFU → video players), use WHIP (ingest) and WHEP (egress) protocols. See Cloudflare Stream integration docs. - -Integrations: R2 for recording `env.R2_BUCKET.put(...)`, Queues for analytics - -Perf: 100-250ms connect, ~50ms latency (95%), 200-400ms glass-to-glass, no participant limit (client: 10-50 tracks) diff --git a/external/develop/devops/cloudflare/references/realtimekit/README.md b/external/develop/devops/cloudflare/references/realtimekit/README.md deleted file mode 100644 index abc6d77..0000000 --- a/external/develop/devops/cloudflare/references/realtimekit/README.md +++ /dev/null @@ -1,113 +0,0 @@ -# Cloudflare RealtimeKit - -Expert guidance for building real-time video and audio applications using **Cloudflare RealtimeKit** - a comprehensive SDK suite for adding customizable live video and voice to web or mobile applications. - -## Overview - -RealtimeKit is Cloudflare's SDK suite built on Realtime SFU, abstracting WebRTC complexity with fast integration, pre-built UI components, global performance (300+ cities), and production features (recording, transcription, chat, polls). - -**Use cases**: Team meetings, webinars, social video, audio calls, interactive plugins - -## Core Concepts - -- **App**: Workspace grouping meetings, participants, presets, recordings. Use separate Apps for staging/production -- **Meeting**: Re-usable virtual room. Each join creates new **Session** -- **Session**: Live meeting instance. Created on first join, ends after last leave -- **Participant**: User added via REST API. Returns `authToken` for client SDK. **Do not reuse tokens** -- **Preset**: Reusable permission/UI template (permissions, meeting type, theme). Applied at participant creation -- **Peer ID** (`id`): Unique per session, changes on rejoin -- **Participant ID** (`userId`): Persistent across sessions - -## Quick Start - -### 1. Create App & Meeting (Backend) - -```bash -# Create app -curl -X POST 'https://api.cloudflare.com/client/v4/accounts//realtime/kit/apps' \ - -H 'Authorization: Bearer ' \ - -d '{"name": "My RealtimeKit App"}' - -# Create meeting -curl -X POST 'https://api.cloudflare.com/client/v4/accounts//realtime/kit//meetings' \ - -H 'Authorization: Bearer ' \ - -d '{"title": "Team Standup"}' - -# Add participant -curl -X POST 'https://api.cloudflare.com/client/v4/accounts//realtime/kit//meetings//participants' \ - -H 'Authorization: Bearer ' \ - -d '{"name": "Alice", "preset_name": "host"}' -# Returns: { authToken } -``` - -### 2. Client Integration - -**React**: -```tsx -import { RtkMeeting } from '@cloudflare/realtimekit-react-ui'; - -function App() { - return {}} />; -} -``` - -**Core SDK**: -```typescript -import RealtimeKitClient from '@cloudflare/realtimekit'; - -const meeting = new RealtimeKitClient({ authToken: '', video: true, audio: true }); -await meeting.join(); -``` - -## Reading Order - -| Task | Files | -|------|-------| -| Quick integration | README only | -| Custom UI | README → patterns → api | -| Backend setup | README → configuration | -| Debug issues | gotchas | -| Advanced features | patterns → api | - -## RealtimeKit vs Realtime SFU - -| Choose | When | -|--------|------| -| **RealtimeKit** | Need pre-built UI, fast integration, React/Angular/HTML | -| **Realtime SFU** | Building from scratch, custom WebRTC, full control | - -RealtimeKit is built on Realtime SFU but abstracts WebRTC complexity with UI components and SDKs. - -## Which Package? - -Need pre-built meeting UI? -- React → `@cloudflare/realtimekit-react-ui` (``) -- Angular → `@cloudflare/realtimekit-angular-ui` -- HTML/Vanilla → `@cloudflare/realtimekit-ui` - -Need custom UI? -- Core SDK → `@cloudflare/realtimekit` (RealtimeKitClient) - full control - -Need raw WebRTC control? -- See `realtime-sfu/` reference - -## In This Reference - -- [Configuration](./configuration.md) - Setup, installation, wrangler config -- [API](./api.md) - Meeting object, REST API, SDK methods -- [Patterns](./patterns.md) - Common workflows, code examples -- [Gotchas](./gotchas.md) - Common issues, troubleshooting - -## See Also - -- [Workers](https://developers.cloudflare.com/workers/) - Backend integration -- [D1](../d1/) - Meeting metadata storage -- [R2](../r2/) - Recording storage -- [KV](../kv/) - Session management - -## Reference Links - -- **Official Docs**: https://developers.cloudflare.com/realtime/realtimekit/ -- **API Reference**: https://developers.cloudflare.com/api/resources/realtime_kit/ -- **Examples**: https://github.com/cloudflare/realtimekit-web-examples -- **Dashboard**: https://dash.cloudflare.com/?to=/:account/realtime/kit diff --git a/external/develop/devops/cloudflare/references/realtimekit/api.md b/external/develop/devops/cloudflare/references/realtimekit/api.md deleted file mode 100644 index 18e9a3f..0000000 --- a/external/develop/devops/cloudflare/references/realtimekit/api.md +++ /dev/null @@ -1,212 +0,0 @@ -# RealtimeKit API Reference - -Complete API reference for Meeting object, REST endpoints, and SDK methods. - -## Meeting Object API - -### `meeting.self` - Local Participant - -```typescript -// Properties: id, userId, name, audioEnabled, videoEnabled, screenShareEnabled, audioTrack, videoTrack, screenShareTracks, roomJoined, roomState -// Methods -await meeting.self.enableAudio() / disableAudio() / enableVideo() / disableVideo() / enableScreenShare() / disableScreenShare() -await meeting.self.setName("Name") // Before join only -await meeting.self.setDevice(device) -const devices = await meeting.self.getAllDevices() / getAudioDevices() / getVideoDevices() / getSpeakerDevices() -// Events: 'roomJoined', 'audioUpdate', 'videoUpdate', 'screenShareUpdate', 'deviceUpdate', 'deviceListUpdate' -meeting.self.on('roomJoined', () => {}) -meeting.self.on('audioUpdate', ({ audioEnabled, audioTrack }) => {}) -``` - -### `meeting.participants` - Remote Participants - -**Collections**: -```typescript -meeting.participants.joined / active / waitlisted / pinned // Maps -const participants = meeting.participants.joined.toArray() -const count = meeting.participants.joined.size() -const p = meeting.participants.joined.get('peer-id') -``` - -**Participant Properties**: -```typescript -participant.id / userId / name -participant.audioEnabled / videoEnabled / screenShareEnabled -participant.audioTrack / videoTrack / screenShareTracks -``` - -**Events**: -```typescript -meeting.participants.joined.on('participantJoined', (participant) => {}) -meeting.participants.joined.on('participantLeft', (participant) => {}) -``` - -### `meeting.meta` - Metadata -```typescript -meeting.meta.meetingId / meetingTitle / meetingStartedTimestamp -``` - -### `meeting.chat` - Chat -```typescript -meeting.chat.messages // Array -await meeting.chat.sendTextMessage("Hello") / sendImageMessage(file) -meeting.chat.on('chatUpdate', ({ message, messages }) => {}) -``` - -### `meeting.polls` - Polling -```typescript -meeting.polls.items // Array -await meeting.polls.create(question, options, anonymous, hideVotes) -await meeting.polls.vote(pollId, optionIndex) -``` - -### `meeting.plugins` - Collaborative Apps -```typescript -meeting.plugins.all // Array -await meeting.plugins.activate(pluginId) / deactivate() -``` - -### `meeting.ai` - AI Features -```typescript -meeting.ai.transcripts // Live transcriptions (when enabled in Preset) -``` - -### Core Methods -```typescript -await meeting.join() // Emits 'roomJoined' on meeting.self -await meeting.leave() -``` - -## TypeScript Types - -```typescript -import type { RealtimeKitClient, States, UIConfig, Participant } from '@cloudflare/realtimekit'; - -// Main interface -interface RealtimeKitClient { - self: SelfState; // Local participant (id, userId, name, audioEnabled, videoEnabled, roomJoined, roomState) - participants: { joined, active, waitlisted, pinned }; // Reactive Maps - chat: ChatNamespace; // messages[], sendTextMessage(), sendImageMessage() - polls: PollsNamespace; // items[], create(), vote() - plugins: PluginsNamespace; // all[], activate(), deactivate() - ai: AINamespace; // transcripts[] - meta: MetaState; // meetingId, meetingTitle, meetingStartedTimestamp - join(): Promise; - leave(): Promise; -} - -// Participant (self & remote share same shape) -interface Participant { - id: string; // Peer ID (changes on rejoin) - userId: string; // Persistent participant ID - name: string; - audioEnabled: boolean; - videoEnabled: boolean; - screenShareEnabled: boolean; - audioTrack: MediaStreamTrack | null; - videoTrack: MediaStreamTrack | null; - screenShareTracks: MediaStreamTrack[]; -} -``` - -## Store Architecture - -RealtimeKit uses reactive store (event-driven updates, live Maps): - -```typescript -// Subscribe to state changes -meeting.self.on('audioUpdate', ({ audioEnabled, audioTrack }) => {}); -meeting.participants.joined.on('participantJoined', (p) => {}); - -// Access current state synchronously -const isAudioOn = meeting.self.audioEnabled; -const count = meeting.participants.joined.size(); -``` - -**Key principles:** State updates emit events after changes. Use `.toArray()` sparingly. Collections are live Maps. - -## REST API - -Base: `https://api.cloudflare.com/client/v4/accounts/{account_id}/realtime/kit/{app_id}` - -### Meetings -```bash -GET /meetings # List all -GET /meetings/{meeting_id} # Get details -POST /meetings # Create: {"title": "..."} -PATCH /meetings/{meeting_id} # Update: {"title": "...", "record_on_start": true} -``` - -### Participants -```bash -GET /meetings/{meeting_id}/participants # List all -GET /meetings/{meeting_id}/participants/{participant_id} # Get details -POST /meetings/{meeting_id}/participants # Add: {"name": "...", "preset_name": "...", "custom_participant_id": "..."} -PATCH /meetings/{meeting_id}/participants/{participant_id} # Update: {"name": "...", "preset_name": "..."} -DELETE /meetings/{meeting_id}/participants/{participant_id} # Delete -POST /meetings/{meeting_id}/participants/{participant_id}/token # Refresh token -``` - -### Active Session -```bash -GET /meetings/{meeting_id}/active-session # Get active session -POST /meetings/{meeting_id}/active-session/kick # Kick users: {"user_ids": ["id1", "id2"]} -POST /meetings/{meeting_id}/active-session/kick-all # Kick all -POST /meetings/{meeting_id}/active-session/poll # Create poll: {"question": "...", "options": [...], "anonymous": false} -``` - -### Recording -```bash -GET /recordings?meeting_id={meeting_id} # List recordings -GET /recordings/active-recording/{meeting_id} # Get active recording -POST /recordings # Start: {"meeting_id": "...", "type": "composite"} (or "track") -PUT /recordings/{recording_id} # Control: {"action": "pause"} (or "resume", "stop") -POST /recordings/track # Track recording: {"meeting_id": "...", "layers": [...]} -``` - -### Livestreaming -```bash -GET /livestreams?exclude_meetings=false # List all -GET /livestreams/{livestream_id} # Get details -POST /meetings/{meeting_id}/livestreams # Start for meeting -POST /meetings/{meeting_id}/active-livestream/stop # Stop -POST /livestreams # Create independent: returns {ingest_server, stream_key, playback_url} -``` - -### Sessions & Analytics -```bash -GET /sessions # List all -GET /sessions/{session_id} # Get details -GET /sessions/{session_id}/participants # List participants -GET /sessions/{session_id}/participants/{participant_id} # Call stats -GET /sessions/{session_id}/chat # Download chat CSV -GET /sessions/{session_id}/transcript # Download transcript CSV -GET /sessions/{session_id}/summary # Get summary -POST /sessions/{session_id}/summary # Generate summary -GET /analytics/daywise?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD # Day-wise analytics -GET /analytics/livestreams/overall # Livestream analytics -``` - -### Webhooks -```bash -GET /webhooks # List all -POST /webhooks # Create: {"url": "https://...", "events": ["session.started", "session.ended"]} -PATCH /webhooks/{webhook_id} # Update -DELETE /webhooks/{webhook_id} # Delete -``` - -## Session Lifecycle - -``` -Initialization → Join Intent → [Waitlist?] → Meeting Screen (Stage) → Ended - ↓ Approved - [Rejected → Ended] -``` - -UI Kit handles state transitions automatically. - -## See Also - -- [Configuration](./configuration.md) - Setup and installation -- [Patterns](./patterns.md) - Usage examples -- [README](./README.md) - Overview and quick start diff --git a/external/develop/devops/cloudflare/references/realtimekit/configuration.md b/external/develop/devops/cloudflare/references/realtimekit/configuration.md deleted file mode 100644 index efbca80..0000000 --- a/external/develop/devops/cloudflare/references/realtimekit/configuration.md +++ /dev/null @@ -1,203 +0,0 @@ -# RealtimeKit Configuration - -Configuration guide for RealtimeKit setup, client SDKs, and wrangler integration. - -## Installation - -### React -```bash -npm install @cloudflare/realtimekit @cloudflare/realtimekit-react-ui -``` - -### Angular -```bash -npm install @cloudflare/realtimekit @cloudflare/realtimekit-angular-ui -``` - -### Web Components/HTML -```bash -npm install @cloudflare/realtimekit @cloudflare/realtimekit-ui -``` - -## Client SDK Configuration - -### React UI Kit -```tsx -import { RtkMeeting } from '@cloudflare/realtimekit-react-ui'; - {}} /> -``` - -### Angular UI Kit -```typescript -@Component({ template: `` }) -export class AppComponent { authToken = ''; onLeave() {} } -``` - -### Web Components -```html - - - -``` - -### Core SDK Configuration -```typescript -import RealtimeKitClient from '@cloudflare/realtimekit'; - -const meeting = new RealtimeKitClient({ - authToken: '', - video: true, audio: true, autoSwitchAudioDevice: true, - mediaConfiguration: { - video: { width: { ideal: 1280 }, height: { ideal: 720 }, frameRate: { ideal: 30 } }, - audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true }, - screenshare: { width: { max: 1920 }, height: { max: 1080 }, frameRate: { ideal: 15 } } - } -}); -await meeting.join(); -``` - -## Backend Setup - -### Create App & Credentials - -**Dashboard**: https://dash.cloudflare.com/?to=/:account/realtime/kit - -**API**: -```bash -curl -X POST 'https://api.cloudflare.com/client/v4/accounts//realtime/kit/apps' \ - -H 'Content-Type: application/json' \ - -H 'Authorization: Bearer ' \ - -d '{"name": "My RealtimeKit App"}' -``` - -**Required Permissions**: API token with **Realtime / Realtime Admin** permissions - -### Create Presets - -```bash -curl -X POST 'https://api.cloudflare.com/client/v4/accounts//realtime/kit//presets' \ - -H 'Authorization: Bearer ' \ - -d '{ - "name": "host", - "permissions": { - "canShareAudio": true, - "canShareVideo": true, - "canRecord": true, - "canLivestream": true, - "canStartStopRecording": true - } - }' -``` - -## Wrangler Configuration - -### Basic Configuration -```jsonc -// wrangler.jsonc -{ - "name": "realtimekit-app", - "main": "src/index.ts", - "compatibility_date": "2025-01-01", // Use current date - "vars": { - "CLOUDFLARE_ACCOUNT_ID": "abc123", - "REALTIMEKIT_APP_ID": "xyz789" - } - // Secrets: wrangler secret put CLOUDFLARE_API_TOKEN -} -``` - -### With Database & Storage -```jsonc -{ - "d1_databases": [{ "binding": "DB", "database_name": "meetings", "database_id": "d1-id" }], - "r2_buckets": [{ "binding": "RECORDINGS", "bucket_name": "recordings" }], - "kv_namespaces": [{ "binding": "SESSIONS", "id": "kv-id" }] -} -``` - -### Multi-Environment -```bash -# Deploy to environments -wrangler deploy --env staging -wrangler deploy --env production -``` - -## TURN Service Configuration - -RealtimeKit can use Cloudflare's TURN service for connectivity through restrictive networks: - -```jsonc -// wrangler.jsonc -{ - "vars": { - "TURN_SERVICE_ID": "your_turn_service_id" - } - // Set secret: wrangler secret put TURN_SERVICE_TOKEN -} -``` - -TURN automatically configured when enabled in account - no client-side changes needed. - -## Theming & Design Tokens - -```typescript -import type { UIConfig } from '@cloudflare/realtimekit'; - -const uiConfig: UIConfig = { - designTokens: { - colors: { - brand: { 500: '#0066ff', 600: '#0052cc' }, - background: { 1000: '#1A1A1A', 900: '#2D2D2D' }, - text: { 1000: '#FFFFFF', 900: '#E0E0E0' } - }, - borderRadius: 'extra-rounded', // 'rounded' | 'extra-rounded' | 'sharp' - theme: 'dark' // 'light' | 'dark' - }, - logo: { url: 'https://example.com/logo.png', altText: 'Company' } -}; - -// Apply to React - {}} /> - -// Or use CSS variables -// :root { --rtk-color-brand-500: #0066ff; --rtk-border-radius: 12px; } -``` - -## Internationalization (i18n) - -### Custom Language Strings -```typescript -import { useLanguage } from '@cloudflare/realtimekit-ui'; - -const customLanguage = { - 'join': 'Entrar', - 'leave': 'Salir', - 'mute': 'Silenciar', - 'unmute': 'Activar audio', - 'turn_on_camera': 'Encender cámara', - 'turn_off_camera': 'Apagar cámara', - 'share_screen': 'Compartir pantalla', - 'stop_sharing': 'Dejar de compartir' -}; - -const t = useLanguage(customLanguage); - -// React usage - {}} /> -``` - -### Supported Locales -Default locales available: `en`, `es`, `fr`, `de`, `pt`, `ja`, `zh` - -```typescript -import { setLocale } from '@cloudflare/realtimekit-ui'; -setLocale('es'); // Switch to Spanish -``` - -## See Also - -- [API](./api.md) - Meeting APIs, REST endpoints -- [Patterns](./patterns.md) - Backend integration examples -- [README](./README.md) - Overview and quick start diff --git a/external/develop/devops/cloudflare/references/realtimekit/gotchas.md b/external/develop/devops/cloudflare/references/realtimekit/gotchas.md deleted file mode 100644 index c6e7dfd..0000000 --- a/external/develop/devops/cloudflare/references/realtimekit/gotchas.md +++ /dev/null @@ -1,169 +0,0 @@ -# RealtimeKit Gotchas & Troubleshooting - -## Common Errors - -### "Cannot connect to meeting" - -**Cause:** Auth token invalid/expired, API credentials lack permissions, or network blocks WebRTC -**Solution:** -Verify token validity, check API token has **Realtime / Realtime Admin** permissions, enable TURN service for restrictive networks - -### "No video/audio tracks" - -**Cause:** Browser permissions not granted, video/audio not enabled, device in use, or device unavailable -**Solution:** -Request browser permissions explicitly, verify initialization config, use `meeting.self.getAllDevices()` to debug, close other apps using device - -### "Participant count mismatched" - -**Cause:** `meeting.participants` doesn't include `meeting.self` -**Solution:** Total count = `meeting.participants.joined.size() + 1` - -### "Events not firing" - -**Cause:** Listeners registered after actions, incorrect event name, or wrong namespace -**Solution:** -Register listeners before calling `meeting.join()`, check event names against docs, verify correct namespace - -### "CORS errors in API calls" - -**Cause:** Making REST API calls from client-side -**Solution:** All REST API calls **must** be server-side (Workers, backend). Never expose API tokens to clients. - -### "Preset not applying" - -**Cause:** Preset doesn't exist, name mismatch (case-sensitive), or participant created before preset -**Solution:** -Verify preset exists via Dashboard or API, check exact spelling and case, create preset before adding participants - -### "Token reuse error" - -**Cause:** Reusing participant tokens across sessions -**Solution:** Generate fresh token per session. Use refresh endpoint if token expires during session. - -### "Video quality poor" - -**Cause:** Insufficient bandwidth, resolution/bitrate too high, or CPU overload -**Solution:** -Lower `mediaConfiguration.video` resolution/frameRate, monitor network conditions, reduce participant count or grid size - -### "Echo or audio feedback" - -**Cause:** Multiple devices picking up same audio source -**Solution:** -- Lower `mediaConfiguration.video` resolution/frameRate -- Monitor network conditions -- Reduce participant count or grid size - -### Issue: Echo or audio feedback -**Cause**: Multiple devices picking up same audio source - -**Solutions**: -Enable `echoCancellation: true` in `mediaConfiguration.audio`, use headphones, mute when not speaking - -### "Screen share not working" - -**Cause:** Browser doesn't support screen sharing API, permission denied, or wrong `displaySurface` config -**Solution:** -Use Chrome/Edge/Firefox (Safari limited support), check browser permissions, try different `displaySurface` values ('window', 'monitor', 'browser') - -### "How do I schedule meetings?" - -**Cause:** RealtimeKit has no built-in scheduling system -**Solution:** -Store meeting IDs in your database with timestamps. Generate participant tokens only when user should join. Example: -```typescript -// Store in DB -{ meetingId: 'abc123', scheduledFor: '2026-02-15T10:00:00Z', userId: 'user456' } - -// Generate token when user clicks "Join" near scheduled time -const response = await fetch('/api/join-meeting', { - method: 'POST', - body: JSON.stringify({ meetingId: 'abc123' }) -}); -const { authToken } = await response.json(); -``` - -### "Recording not starting" - -**Cause:** Preset lacks recording permissions, no active session, or API call from client -**Solution:** -Verify preset has `canRecord: true` and `canStartStopRecording: true`, ensure session is active (at least one participant), make recording API calls server-side only - -## Limits - -| Resource | Limit | -|----------|-------| -| Max participants per session | 100 | -| Max concurrent sessions per App | 1000 | -| Max recording duration | 6 hours | -| Max meeting duration | 24 hours | -| Max chat message length | 4000 characters | -| Max preset name length | 64 characters | -| Max meeting title length | 256 characters | -| Max participant name length | 256 characters | -| Token expiration | 24 hours (default) | -| WebRTC ports required | UDP 1024-65535 | - -## Network Requirements - -### Firewall Rules -Allow outbound UDP/TCP to: -- `*.cloudflare.com` ports 443, 80 -- UDP ports 1024-65535 (WebRTC media) - -### TURN Service -Enable for users behind restrictive firewalls/proxies: -```jsonc -// wrangler.jsonc -{ - "vars": { - "TURN_SERVICE_ID": "your_turn_service_id" - } - // Set secret: wrangler secret put TURN_SERVICE_TOKEN -} -``` - -TURN automatically configured in SDK when enabled in account. - -## Debugging Tips - -```typescript -// Check devices -const devices = await meeting.self.getAllDevices(); -meeting.self.on('deviceListUpdate', ({ added, removed, devices }) => console.log('Devices:', { added, removed, devices })); - -// Monitor participants -meeting.participants.joined.on('participantJoined', (p) => console.log(`${p.name} joined:`, { id: p.id, userId: p.userId, audioEnabled: p.audioEnabled, videoEnabled: p.videoEnabled })); - -// Check room state -meeting.self.on('roomJoined', () => console.log('Room:', { meetingId: meeting.meta.meetingId, meetingTitle: meeting.meta.meetingTitle, participantCount: meeting.participants.joined.size() + 1, audioEnabled: meeting.self.audioEnabled, videoEnabled: meeting.self.videoEnabled })); - -// Log all events -['roomJoined', 'audioUpdate', 'videoUpdate', 'screenShareUpdate', 'deviceUpdate', 'deviceListUpdate'].forEach(event => meeting.self.on(event, (data) => console.log(`[self] ${event}:`, data))); -['participantJoined', 'participantLeft'].forEach(event => meeting.participants.joined.on(event, (data) => console.log(`[participants] ${event}:`, data))); -meeting.chat.on('chatUpdate', (data) => console.log('[chat] chatUpdate:', data)); -``` - -## Security & Performance - -### Security: Do NOT -- Expose `CLOUDFLARE_API_TOKEN` in client code, hardcode credentials in frontend -- Reuse participant tokens, store tokens in localStorage without encryption -- Allow client-side meeting creation - -### Security: DO -- Generate tokens server-side only, use HTTPS, implement rate limiting -- Validate user auth before generating tokens, use `custom_participant_id` to map to your user system -- Set appropriate preset permissions per user role, rotate API tokens regularly - -### Performance -- **CPU**: Lower video resolution/frameRate, disable video for audio-only, use `meeting.participants.active` for large meetings, implement virtual scrolling -- **Bandwidth**: Set max resolution in `mediaConfiguration`, disable screenshare audio if unneeded, use audio-only mode, implement adaptive bitrate -- **Memory**: Clean up event listeners on unmount, call `meeting.leave()` when done, don't store large participant arrays - -## In This Reference -- [README.md](README.md) - Overview, core concepts, quick start -- [configuration.md](configuration.md) - SDK config, presets, wrangler setup -- [api.md](api.md) - Client SDK APIs, REST endpoints -- [patterns.md](patterns.md) - Common patterns, React hooks, backend integration diff --git a/external/develop/devops/cloudflare/references/realtimekit/patterns.md b/external/develop/devops/cloudflare/references/realtimekit/patterns.md deleted file mode 100644 index ac662ef..0000000 --- a/external/develop/devops/cloudflare/references/realtimekit/patterns.md +++ /dev/null @@ -1,223 +0,0 @@ -# RealtimeKit Patterns - -## UI Kit (Minimal Code) - -```tsx -// React -import { RtkMeeting } from '@cloudflare/realtimekit-react-ui'; - console.log('Left')} /> - -// Angular -@Component({ template: `` }) -export class AppComponent { authToken = ''; onLeave(event: unknown) {} } - -// HTML/Web Components - - - -``` - -## UI Components - -RealtimeKit provides 133+ pre-built Stencil.js Web Components with framework wrappers: - -### Layout Components -- `` - Full meeting UI (all-in-one) -- ``, ``, `` - Layout sections -- `` - Chat/participants sidebar -- `` - Adaptive video grid - -### Control Components -- ``, `` - Media controls -- `` - Screen sharing -- `` - Leave meeting -- `` - Device settings - -### Grid Variants -- `` - Active speaker focus -- `` - Audio-only mode -- `` - Paginated layout - -**See full catalog**: https://docs.realtime.cloudflare.com/ui-kit - -## Core SDK Patterns - -### Basic Setup -```typescript -import RealtimeKitClient from '@cloudflare/realtimekit'; - -const meeting = new RealtimeKitClient({ authToken, video: true, audio: true }); -meeting.self.on('roomJoined', () => console.log('Joined:', meeting.meta.meetingTitle)); -meeting.participants.joined.on('participantJoined', (p) => console.log(`${p.name} joined`)); -await meeting.join(); -``` - -### Video Grid & Device Selection -```typescript -// Video grid -function VideoGrid({ meeting }) { - const [participants, setParticipants] = useState([]); - useEffect(() => { - const update = () => setParticipants(meeting.participants.joined.toArray()); - meeting.participants.joined.on('participantJoined', update); - meeting.participants.joined.on('participantLeft', update); - update(); - return () => { meeting.participants.joined.off('participantJoined', update); meeting.participants.joined.off('participantLeft', update); }; - }, [meeting]); - return
- {participants.map(p => )} -
; -} - -function VideoTile({ participant }) { - const videoRef = useRef(null); - useEffect(() => { - if (videoRef.current && participant.videoTrack) videoRef.current.srcObject = new MediaStream([participant.videoTrack]); - }, [participant.videoTrack]); - return
; -} - -// Device selection -const devices = await meeting.self.getAllDevices(); -const switchCamera = (deviceId: string) => { - const device = devices.find(d => d.deviceId === deviceId); - if (device) await meeting.self.setDevice(device); -}; -``` - -## React Hooks (Official) - -```typescript -import { useRealtimeKitClient, useRealtimeKitSelector } from '@cloudflare/realtimekit-react-ui'; - -function MyComponent() { - const [meeting, initMeeting] = useRealtimeKitClient(); - const audioEnabled = useRealtimeKitSelector(m => m.self.audioEnabled); - const participantCount = useRealtimeKitSelector(m => m.participants.joined.size()); - - useEffect(() => { initMeeting({ authToken: '' }); }, []); - - return
- - {participantCount} participants -
; -} -``` - -**Benefits:** Automatic re-renders, memoized selectors, type-safe - -## Waitlist Handling - -```typescript -// Monitor waitlist -meeting.participants.waitlisted.on('participantJoined', (participant) => { - console.log(`${participant.name} is waiting`); - // Show admin UI to approve/reject -}); - -// Approve from waitlist (backend only) -await fetch( - `https://api.cloudflare.com/client/v4/accounts/${accountId}/realtime/kit/${appId}/meetings/${meetingId}/active-session/waitlist/approve`, - { - method: 'POST', - headers: { 'Authorization': `Bearer ${apiToken}` }, - body: JSON.stringify({ user_ids: [participant.userId] }) - } -); - -// Client receives automatic transition when approved -meeting.self.on('roomJoined', () => console.log('Approved and joined')); -``` - -## Audio-Only Mode - -```typescript -const meeting = new RealtimeKitClient({ - authToken: '', - video: false, // Disable video - audio: true, - mediaConfiguration: { - audio: { - echoCancellation: true, - noiseSuppression: true, - autoGainControl: true - } - } -}); - -// Use audio grid component -import { RtkAudioGrid } from '@cloudflare/realtimekit-react-ui'; - -``` - -## Addon System - -```typescript -// List available addons -meeting.plugins.all.forEach(plugin => { - console.log(plugin.id, plugin.name, plugin.active); -}); - -// Activate collaborative app -await meeting.plugins.activate('whiteboard-addon-id'); - -// Listen for activations -meeting.plugins.on('pluginActivated', ({ plugin }) => { - console.log(`${plugin.name} activated`); -}); - -// Deactivate -await meeting.plugins.deactivate(); -``` - -## Backend Integration - -### Token Generation (Workers) -```typescript -export interface Env { CLOUDFLARE_API_TOKEN: string; CLOUDFLARE_ACCOUNT_ID: string; REALTIMEKIT_APP_ID: string; } - -export default { - async fetch(request: Request, env: Env): Promise { - const url = new URL(request.url); - - if (url.pathname === '/api/join-meeting') { - const { meetingId, userName, presetName } = await request.json(); - const response = await fetch( - `https://api.cloudflare.com/client/v4/accounts/${env.CLOUDFLARE_ACCOUNT_ID}/realtime/kit/${env.REALTIMEKIT_APP_ID}/meetings/${meetingId}/participants`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${env.CLOUDFLARE_API_TOKEN}` }, - body: JSON.stringify({ name: userName, preset_name: presetName }) - } - ); - const data = await response.json(); - return Response.json({ authToken: data.result.authToken }); - } - - return new Response('Not found', { status: 404 }); - } -}; -``` - -## Best Practices - -### Security -1. **Never expose API tokens client-side** - Generate participant tokens server-side only -2. **Don't reuse participant tokens** - Generate fresh token per session, use refresh endpoint if expired -3. **Use custom participant IDs** - Map to your user system for cross-session tracking - -### Performance -1. **Event-driven updates** - Listen to events, don't poll. Use `toArray()` only when needed -2. **Media quality constraints** - Set appropriate resolution/bitrate limits based on network conditions -3. **Device management** - Enable `autoSwitchAudioDevice` for better UX, handle device list updates - -### Architecture -1. **Separate Apps for environments** - staging vs production to prevent data mixing -2. **Preset strategy** - Create presets at App level, reuse across meetings -3. **Token management** - Backend generates tokens, frontend receives via authenticated endpoint - -## In This Reference -- [README.md](README.md) - Overview, core concepts, quick start -- [configuration.md](configuration.md) - SDK config, presets, wrangler setup -- [api.md](api.md) - Client SDK APIs, REST endpoints -- [gotchas.md](gotchas.md) - Common issues, troubleshooting, limits diff --git a/external/develop/devops/cloudflare/references/secrets-store/README.md b/external/develop/devops/cloudflare/references/secrets-store/README.md index b4ff885..9d463c1 100644 --- a/external/develop/devops/cloudflare/references/secrets-store/README.md +++ b/external/develop/devops/cloudflare/references/secrets-store/README.md @@ -1,6 +1,6 @@ # Cloudflare Secrets Store -Use Secrets Store for account-level credentials shared across Workers or supported integrations. Use [Worker secrets](https://developers.cloudflare.com/workers/configuration/secrets/) when credentials belong to one Worker and do not need centralized sharing. +Use Secrets Store for account-level credentials shared across Workers or supported integrations. Use [Worker secrets](https://developers.cloudflare.com/workers/configuration/secrets/index.md) when credentials belong to one Worker and do not need centralized sharing. Fetch the relevant documentation before implementing. Current Cloudflare docs are the source of truth for binding APIs, management commands, permissions, availability, and quotas. Use the [Secrets Store documentation index](https://developers.cloudflare.com/secrets-store/llms.txt) to discover additional guidance. @@ -8,7 +8,7 @@ Fetch the relevant documentation before implementing. Current Cloudflare docs ar - Share a secret only among services that should use the same credential and rotate together. - Separate development, staging, and production credentials; select the intended account and environment before managing or binding a secret. -- Grant only the management permissions and consuming-service scopes needed. Permission to view metadata does not imply permission to bind or retrieve a value; fetch [access control](https://developers.cloudflare.com/secrets-store/access-control/) for the current rules. +- Grant only the management permissions and consuming-service scopes needed. Permission to view metadata does not imply permission to bind or retrieve a value; fetch [access control](https://developers.cloudflare.com/secrets-store/access-control/index.md) for the current rules. ## Read by task @@ -19,4 +19,4 @@ Fetch the relevant documentation before implementing. Current Cloudflare docs ar | Plan rotation, migration, encryption, or auditing | [patterns.md](./patterns.md) | | Diagnose access, deployment, or quota failures | [gotchas.md](./gotchas.md) | -Fetch the [product overview](https://developers.cloudflare.com/secrets-store/) for current availability and supported integrations. For AI Gateway provider credentials, use [Bring your own keys](https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/). +Fetch the [product overview](https://developers.cloudflare.com/secrets-store/index.md) for current availability and supported integrations. For AI Gateway provider credentials, use [Bring your own keys](https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/index.md). diff --git a/external/develop/devops/cloudflare/references/secrets-store/api.md b/external/develop/devops/cloudflare/references/secrets-store/api.md index 8a04baa..3905bfa 100644 --- a/external/develop/devops/cloudflare/references/secrets-store/api.md +++ b/external/develop/devops/cloudflare/references/secrets-store/api.md @@ -4,15 +4,15 @@ Fetch the current API documentation before implementing calls or copying types. | Task | Documentation | |------|---------------| -| Read a bound account secret asynchronously in a Worker | [Workers integration: access the secret](https://developers.cloudflare.com/secrets-store/integrations/workers/#3-access-the-secret-on-the-env-object) | -| Generate binding and runtime types for the Worker configuration | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/) | -| Manage stores and secrets, inspect metadata, or query quota | [Secrets Store REST API](https://developers.cloudflare.com/api/resources/secrets_store/) | -| Choose authorization and consuming-service scope | [Access control](https://developers.cloudflare.com/secrets-store/access-control/) | -| Manage secrets through the CLI instead of REST | [Wrangler Secrets Store commands](https://developers.cloudflare.com/workers/wrangler/commands/secrets-store/) | +| Read a bound account secret asynchronously in a Worker | [Workers integration: access the secret](https://developers.cloudflare.com/secrets-store/integrations/workers/index.md#3-access-the-secret-on-the-env-object) | +| Generate binding and runtime types for the Worker configuration | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/index.md) | +| Manage stores and secrets, inspect metadata, or query quota | [Secrets Store REST API](https://developers.cloudflare.com/api/resources/secrets_store/index.md) | +| Choose authorization and consuming-service scope | [Access control](https://developers.cloudflare.com/secrets-store/access-control/index.md) | +| Manage secrets through the CLI instead of REST | [Wrangler Secrets Store commands](https://developers.cloudflare.com/workers/wrangler/commands/secrets-store/index.md) | ## Runtime decisions -Account-secret bindings require asynchronous retrieval; they are not the direct string values exposed by [Worker secrets](https://developers.cloudflare.com/workers/configuration/secrets/). Account management APIs and metadata reads are separate from consuming a bound secret in a Worker. +Account-secret bindings require asynchronous retrieval; they are not the direct string values exposed by [Worker secrets](https://developers.cloudflare.com/workers/configuration/secrets/index.md). Account management APIs and metadata reads are separate from consuming a bound secret in a Worker. Handle retrieval failures at the application's error boundary without exposing credentials. Reuse a retrieved value within the request when useful; avoid long-lived application caches that could keep revoked credentials in use. Validate structured secret values against the application's schema before using them. diff --git a/external/develop/devops/cloudflare/references/secrets-store/configuration.md b/external/develop/devops/cloudflare/references/secrets-store/configuration.md index a50f65e..dd4405e 100644 --- a/external/develop/devops/cloudflare/references/secrets-store/configuration.md +++ b/external/develop/devops/cloudflare/references/secrets-store/configuration.md @@ -6,19 +6,19 @@ Fetch the guide for the operation you are performing before writing configuratio | Task | Documentation | |------|---------------| -| Create a store and secret, then bind it through Wrangler or the dashboard | [Workers integration](https://developers.cloudflare.com/secrets-store/integrations/workers/) | -| Create, edit, duplicate, or delete account secrets | [Manage secrets](https://developers.cloudflare.com/secrets-store/manage-secrets/how-to/) | -| Look up current store/secret command syntax and local versus remote flags | [Wrangler Secrets Store commands](https://developers.cloudflare.com/workers/wrangler/commands/secrets-store/) | -| Configure bindings for each deployment environment | [Wrangler environments](https://developers.cloudflare.com/workers/wrangler/environments/) | -| Choose user roles, CI token permissions, and secret scopes | [Secrets Store access control](https://developers.cloudflare.com/secrets-store/access-control/) | +| Create a store and secret, then bind it through Wrangler or the dashboard | [Workers integration](https://developers.cloudflare.com/secrets-store/integrations/workers/index.md) | +| Create, edit, duplicate, or delete account secrets | [Manage secrets](https://developers.cloudflare.com/secrets-store/manage-secrets/how-to/index.md) | +| Look up current store/secret command syntax and local versus remote flags | [Wrangler Secrets Store commands](https://developers.cloudflare.com/workers/wrangler/commands/secrets-store/index.md) | +| Configure bindings for each deployment environment | [Wrangler environments](https://developers.cloudflare.com/workers/wrangler/environments/index.md) | +| Choose user roles, CI token permissions, and secret scopes | [Secrets Store access control](https://developers.cloudflare.com/secrets-store/access-control/index.md) | Treat the store ID, secret ID, secret name, and Worker binding name as different identifiers. Use the identifier required by the documented operation; do not infer update or delete flags from the create command. ## Local development and deployment -Secrets Store management commands default to local state; production operations use the documented remote option. Local development needs separately provisioned local secrets. Follow the local-development notes in [Workers integration](https://developers.cloudflare.com/secrets-store/integrations/workers/) and the [command reference](https://developers.cloudflare.com/workers/wrangler/commands/secrets-store/). +Secrets Store management commands default to local state; production operations use the documented remote option. Local development needs separately provisioned local secrets. Follow the local-development notes in [Workers integration](https://developers.cloudflare.com/secrets-store/integrations/workers/index.md) and the [command reference](https://developers.cloudflare.com/workers/wrangler/commands/secrets-store/index.md). -Check the selected account, deployment environment, secret scope, and binding configuration before deploying. In CI, distinguish permission to read metadata from permission to attach a secret to a Worker; use the [CI/CD access-control guidance](https://developers.cloudflare.com/secrets-store/access-control/#api-token-permissions). +Check the selected account, deployment environment, secret scope, and binding configuration before deploying. In CI, distinguish permission to read metadata from permission to attach a secret to a Worker; use the [CI/CD access-control guidance](https://developers.cloudflare.com/secrets-store/access-control/index.md#api-token-permissions). Use protected secret input rather than putting credential values in command arguments, source files, or CI logs. For interactive CLI use, follow the command reference's secret-value prompt guidance. Keep local credentials out of version control. diff --git a/external/develop/devops/cloudflare/references/secrets-store/gotchas.md b/external/develop/devops/cloudflare/references/secrets-store/gotchas.md index a8ae652..e6fbdeb 100644 --- a/external/develop/devops/cloudflare/references/secrets-store/gotchas.md +++ b/external/develop/devops/cloudflare/references/secrets-store/gotchas.md @@ -4,16 +4,16 @@ Start with the failing operation and fetch its documentation before changing cre | Symptom or decision | What to check | Documentation | |---------------------|---------------|---------------| -| Deployment cannot attach a secret | Selected account, caller's binding permission, and the secret's consuming-service scope; metadata read permission alone is insufficient | [Access control](https://developers.cloudflare.com/secrets-store/access-control/) | -| Secret is missing or the wrong value is used | Store, secret name, binding name, and selected deployment environment | [Workers integration](https://developers.cloudflare.com/secrets-store/integrations/workers/), [Wrangler environments](https://developers.cloudflare.com/workers/wrangler/environments/) | -| Secret works in production but fails locally | Local secret provisioning and the management command's local/remote target | [Workers integration](https://developers.cloudflare.com/secrets-store/integrations/workers/), [Wrangler commands](https://developers.cloudflare.com/workers/wrangler/commands/secrets-store/) | -| CLI update, retrieval, or deletion fails | The operation's required identifier and flags; a secret name is not interchangeable with its ID | [Wrangler commands](https://developers.cloudflare.com/workers/wrangler/commands/secrets-store/) | -| Binding is treated as a string or has incorrect types | Asynchronous account-secret access and generated configuration types | [Runtime access](https://developers.cloudflare.com/secrets-store/integrations/workers/#3-access-the-secret-on-the-env-object), [TypeScript](https://developers.cloudflare.com/workers/languages/typescript/) | -| Quota or value-size validation fails | Current account quota and the requested operation's schema | [Secrets Store REST API](https://developers.cloudflare.com/api/resources/secrets_store/) | -| Regional or integration support is unclear | Current product availability and supported consumers | [Product overview](https://developers.cloudflare.com/secrets-store/) | +| Deployment cannot attach a secret | Selected account, caller's binding permission, and the secret's consuming-service scope; metadata read permission alone is insufficient | [Access control](https://developers.cloudflare.com/secrets-store/access-control/index.md) | +| Secret is missing or the wrong value is used | Store, secret name, binding name, and selected deployment environment | [Workers integration](https://developers.cloudflare.com/secrets-store/integrations/workers/index.md), [Wrangler environments](https://developers.cloudflare.com/workers/wrangler/environments/index.md) | +| Secret works in production but fails locally | Local secret provisioning and the management command's local/remote target | [Workers integration](https://developers.cloudflare.com/secrets-store/integrations/workers/index.md), [Wrangler commands](https://developers.cloudflare.com/workers/wrangler/commands/secrets-store/index.md) | +| CLI update, retrieval, or deletion fails | The operation's required identifier and flags; a secret name is not interchangeable with its ID | [Wrangler commands](https://developers.cloudflare.com/workers/wrangler/commands/secrets-store/index.md) | +| Binding is treated as a string or has incorrect types | Asynchronous account-secret access and generated configuration types | [Runtime access](https://developers.cloudflare.com/secrets-store/integrations/workers/index.md#3-access-the-secret-on-the-env-object), [TypeScript](https://developers.cloudflare.com/workers/languages/typescript/index.md) | +| Quota or value-size validation fails | Current account quota and the requested operation's schema | [Secrets Store REST API](https://developers.cloudflare.com/api/resources/secrets_store/index.md) | +| Regional or integration support is unclear | Current product availability and supported consumers | [Product overview](https://developers.cloudflare.com/secrets-store/index.md) | Do not diagnose failures by printing secret values or returning raw errors to clients. Check metadata and sanitized operation context. Validate JSON or other structured values before consuming them, and handle retrieval or parsing failures at the application's error boundary. -Before deleting a secret to fix a binding conflict or quota problem, identify all consumers. Follow [secret management](https://developers.cloudflare.com/secrets-store/manage-secrets/how-to/) and the rotation decisions in [patterns.md](./patterns.md); removing a shared credential can affect multiple services. +Before deleting a secret to fix a binding conflict or quota problem, identify all consumers. Follow [secret management](https://developers.cloudflare.com/secrets-store/manage-secrets/how-to/index.md) and the rotation decisions in [patterns.md](./patterns.md); removing a shared credential can affect multiple services. See [configuration.md](./configuration.md) for setup and [api.md](./api.md) for API selection. diff --git a/external/develop/devops/cloudflare/references/secrets-store/patterns.md b/external/develop/devops/cloudflare/references/secrets-store/patterns.md index 88e4dcb..538f4e9 100644 --- a/external/develop/devops/cloudflare/references/secrets-store/patterns.md +++ b/external/develop/devops/cloudflare/references/secrets-store/patterns.md @@ -4,24 +4,24 @@ Use these decisions to choose the relevant guides; fetch the linked documentatio ## Rotation and sharing -Fetch [edit, duplicate, and delete operations](https://developers.cloudflare.com/secrets-store/manage-secrets/how-to/) and [Workers binding setup](https://developers.cloudflare.com/secrets-store/integrations/workers/) before changing a credential. Editing a shared secret affects every service using it. Inventory consumers and coordinate the change with the credential's issuer. +Fetch [edit, duplicate, and delete operations](https://developers.cloudflare.com/secrets-store/manage-secrets/how-to/index.md) and [Workers binding setup](https://developers.cloudflare.com/secrets-store/integrations/workers/index.md) before changing a credential. Editing a shared secret affects every service using it. Inventory consumers and coordinate the change with the credential's issuer. An application rotation plan must account for old and new credential validity, consumer rollout, verification, rollback, and eventual revocation. The management guide describes secret operations, not an end-to-end zero-downtime rotation protocol. Do not retry arbitrary failed requests with an old key: retries must respect upstream authentication semantics and the operation's idempotency. ## Migrate from Worker secrets -Read [Worker secrets](https://developers.cloudflare.com/workers/configuration/secrets/) alongside [Secrets Store Workers integration](https://developers.cloudflare.com/secrets-store/integrations/workers/). Migration changes both the binding configuration and access from a direct value to asynchronous retrieval. Verify the new binding in staging, resolve naming conflicts during rollout, and remove the old secret only after consumers have switched successfully. +Read [Worker secrets](https://developers.cloudflare.com/workers/configuration/secrets/index.md) alongside [Secrets Store Workers integration](https://developers.cloudflare.com/secrets-store/integrations/workers/index.md). Migration changes both the binding configuration and access from a direct value to asynchronous retrieval. Verify the new binding in staging, resolve naming conflicts during rollout, and remove the old secret only after consumers have switched successfully. ## Encryption and signing -For cryptographic operations, fetch [Workers Web Crypto](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) and the [request-signing example](https://developers.cloudflare.com/workers/examples/signing-requests/). Use the [Secrets Store integration](https://developers.cloudflare.com/secrets-store/integrations/workers/) to retrieve key material. +For cryptographic operations, fetch [Workers Web Crypto](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/index.md) and the [request-signing example](https://developers.cloudflare.com/workers/examples/signing-requests/index.md). Use the [Secrets Store integration](https://developers.cloudflare.com/secrets-store/integrations/workers/index.md) to retrieve key material. These pages cover the runtime primitives; they do not define an application's encrypted KV format, key lifecycle, or signing protocol. Choose those explicitly, including key encoding, nonce handling, verification, and rotation of data encrypted under old keys. Validate JSON secrets at runtime rather than relying on a TypeScript assertion. ## Audit and integrations -Fetch [Secrets Store audit logs](https://developers.cloudflare.com/secrets-store/audit-logs/) for the recorded actions and how to inspect them. Keep application telemetry free of credential values; do not assume account audit events replace application-level success and failure monitoring. +Fetch [Secrets Store audit logs](https://developers.cloudflare.com/secrets-store/audit-logs/index.md) for the recorded actions and how to inspect them. Keep application telemetry free of credential values; do not assume account audit events replace application-level success and failure monitoring. -For AI Gateway credentials, use [Bring your own keys](https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/). For an internal authentication service, consult [Worker service bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) and define the application's authorization boundary before exposing signing or secret-backed operations. +For AI Gateway credentials, use [Bring your own keys](https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/index.md). For an internal authentication service, consult [Worker service bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/index.md) and define the application's authorization boundary before exposing signing or secret-backed operations. See [configuration.md](./configuration.md) for permissions and environments, and [gotchas.md](./gotchas.md) for troubleshooting. diff --git a/external/develop/devops/cloudflare/references/skill-feedback.md b/external/develop/devops/cloudflare/references/skill-feedback.md new file mode 100644 index 0000000..502b003 --- /dev/null +++ b/external/develop/devops/cloudflare/references/skill-feedback.md @@ -0,0 +1,41 @@ +# Skill feedback + +GitHub issues are public: anyone can read an issue once it is posted. Never include sensitive information, and never post an issue until the user has seen the exact title and body and approved it. + +Use this when the user reports that the `cloudflare` skill gave incorrect or outdated guidance, is missing information, or could be improved. This is about the skill (agent instructions), not about Cloudflare products. + +## What not to include + +The issue is drafted from the conversation, so remove the following before drafting and describe the problem in general terms instead: + +- Credentials, API tokens, secrets, and passwords +- Account IDs, zone IDs, and other resource identifiers +- Customer or personal data, such as names, email addresses, and IP addresses +- Internal hostnames and URLs +- Private code, configuration, and logs + +## Steps + +1. **Ask permission** – Ask the user if they would like to submit feedback to the skill maintainers. If they decline, move on. + +2. **Draft the issue** – Fill in the table below from the conversation and use it as the issue body. Always identify which specific file and section caused the problem. Leave out everything listed in [What not to include](#what-not-to-include). + +3. **Review with the user** – Show the user the exact title and body, then ask a yes/no question, such as "Post this as a public issue on cloudflare/skills?" Submit only after the user answers yes. If they ask for changes, apply them and ask again. If they answer no, do not post. Agreeing to send feedback in step 1 is not approval of the text. + +4. **Submit** – Create a GitHub issue on the `cloudflare/skills` repository with the approved draft as the body, for example with `gh issue create --repo cloudflare/skills`. The title must follow this format: `skill-feedback: cloudflare: `. + +5. **Share the result** – Share the issue URL with the user after submission. If submission fails, give the user the approved title and body, and this link to create the issue manually: + +## Issue body + +| Field | Value | +|-------|-------| +| Type | `bug` (incorrect guidance), `outdated`, `missing`, or `improvement` | +| Skill | `cloudflare` | +| Source | File and section, e.g. `SKILL.md` > "Section name" or `references/.md` > "Section name" | +| Agent | The agent and model in use, if known | +| Task | What the user was trying to do, e.g. "deploy a preview" | +| Expected | What should have happened, e.g. "Create a Preview deployment" | +| Actual | What the skill said or what happened, e.g. "Unknown option: --preview" | +| Impact | The effect on the user, e.g. "Time spent finding the correct syntax" | +| Suggested fix | How the skill should change; leave blank if unsure | diff --git a/external/develop/devops/cloudflare/references/smart-placement/README.md b/external/develop/devops/cloudflare/references/smart-placement/README.md deleted file mode 100644 index 72f4e6c..0000000 --- a/external/develop/devops/cloudflare/references/smart-placement/README.md +++ /dev/null @@ -1,138 +0,0 @@ -# Cloudflare Workers Smart Placement - -Automatic workload placement optimization to minimize latency by running Workers closer to backend infrastructure rather than end users. - -## Core Concept - -Smart Placement automatically analyzes Worker request duration across Cloudflare's global network and intelligently routes requests to optimal data center locations. Instead of defaulting to the location closest to the end user, Smart Placement can forward requests to locations closer to backend infrastructure when this reduces overall request duration. - -### When to Use - -**Enable Smart Placement when:** -- Worker makes multiple round trips to backend services/databases -- Backend infrastructure is geographically concentrated -- Request duration dominated by backend latency rather than network latency from user -- Running backend logic in Workers (APIs, data aggregation, SSR with DB calls) -- Worker uses `fetch` handler (not RPC methods) - -**Do NOT enable for:** -- Workers serving only static content or cached responses -- Workers without significant backend communication -- Pure edge logic (auth checks, redirects, simple transformations) -- Workers without fetch event handlers -- Workers with RPC methods or named entrypoints (only `fetch` handlers are affected) -- Pages/Assets Workers with `run_worker_first = true` (degrades asset serving) - -### Decision Tree - -``` -Does your Worker have a fetch handler? -├─ No → Smart Placement won't work (skip) -└─ Yes - │ - Does it make multiple backend calls (DB/API)? - ├─ No → Don't enable (won't help) - └─ Yes - │ - Is backend geographically concentrated? - ├─ No (globally distributed) → Probably won't help - └─ Yes or uncertain - │ - Does it serve static assets with run_worker_first=true? - ├─ Yes → Don't enable (will hurt performance) - └─ No → Enable Smart Placement - │ - After 15min, check placement_status - ├─ SUCCESS → Monitor metrics - ├─ INSUFFICIENT_INVOCATIONS → Need more traffic - └─ UNSUPPORTED_APPLICATION → Disable (hurting performance) -``` - -### Key Architecture Pattern - -**Recommended:** Split full-stack applications into separate Workers: -``` -User → Frontend Worker (at edge, close to user) - ↓ Service Binding - Backend Worker (Smart Placement enabled, close to DB/API) - ↓ - Database/Backend Service -``` - -This maintains fast, reactive frontends while optimizing backend latency. - -## Quick Start - -```jsonc -// wrangler.jsonc -{ - "placement": { - "mode": "smart" // or "off" to explicitly disable - } -} -``` - -Deploy and wait 15 minutes for analysis. Check status via API or dashboard metrics. - -**To disable:** Set `"mode": "off"` or remove `placement` field entirely (both equivalent). - -## Requirements - -- Wrangler 2.20.0+ -- Analysis time: Up to 15 minutes after enabling -- Traffic requirements: Consistent traffic from multiple global locations -- Available on all Workers plans (Free, Paid, Enterprise) - -## Placement Status Values - -```typescript -type PlacementStatus = - | undefined // Not yet analyzed - | 'SUCCESS' // Successfully optimized - | 'INSUFFICIENT_INVOCATIONS' // Not enough traffic - | 'UNSUPPORTED_APPLICATION'; // Made Worker slower (reverted) -``` - -## CLI Commands - -```bash -# Deploy with Smart Placement -wrangler deploy - -# Check placement status -curl -H "Authorization: Bearer $TOKEN" \ - https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/services/$WORKER_NAME \ - | jq .result.placement_status - -# Monitor -wrangler tail your-worker-name --header cf-placement -``` - -## Reading Order - -**First time?** Start here: -1. This README - understand core concepts and when to use Smart Placement -2. [configuration.md](./configuration.md) - set up wrangler.jsonc and understand limitations -3. [patterns.md](./patterns.md) - see practical examples for your use case -4. [api.md](./api.md) - monitor and verify Smart Placement is working -5. [gotchas.md](./gotchas.md) - troubleshoot common issues - -**Quick lookup:** -- "Should I enable Smart Placement?" → See "When to Use" above -- "How do I configure it?" → [configuration.md](./configuration.md) -- "How do I split frontend/backend?" → [patterns.md](./patterns.md) -- "Why isn't it working?" → [gotchas.md](./gotchas.md) - -## In This Reference - -- [configuration.md](./configuration.md) - wrangler.jsonc setup, mode values, validation rules -- [api.md](./api.md) - Placement Status API, cf-placement header, monitoring -- [patterns.md](./patterns.md) - Frontend/backend split, database workers, SSR patterns -- [gotchas.md](./gotchas.md) - Troubleshooting INSUFFICIENT_INVOCATIONS, performance issues - -## See Also - -- [workers](https://developers.cloudflare.com/workers/) - Worker runtime and fetch handlers -- [d1](../d1/) - D1 database that benefits from Smart Placement -- [durable-objects](https://developers.cloudflare.com/durable-objects/) - Durable Objects with backend logic -- [bindings](../bindings/) - Service bindings for frontend/backend split diff --git a/external/develop/devops/cloudflare/references/smart-placement/api.md b/external/develop/devops/cloudflare/references/smart-placement/api.md deleted file mode 100644 index 6608985..0000000 --- a/external/develop/devops/cloudflare/references/smart-placement/api.md +++ /dev/null @@ -1,183 +0,0 @@ -# Smart Placement API - -## Placement Status API - -Query Worker placement status via Cloudflare API: - -```bash -curl -X GET "https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/workers/services/{WORKER_NAME}" \ - -H "Authorization: Bearer " \ - -H "Content-Type: application/json" -``` - -Response includes `placement_status` field: - -```typescript -type PlacementStatus = - | undefined // Not yet analyzed - | 'SUCCESS' // Successfully optimized - | 'INSUFFICIENT_INVOCATIONS' // Not enough traffic - | 'UNSUPPORTED_APPLICATION'; // Made Worker slower (reverted) -``` - -## Status Meanings - -**`undefined` (not present)** -- Worker not yet analyzed -- Always runs at default edge location closest to user - -**`SUCCESS`** -- Analysis complete, Smart Placement active -- Worker runs in optimal location (may be edge or remote) - -**`INSUFFICIENT_INVOCATIONS`** -- Not enough requests to make placement decision -- Requires consistent multi-region traffic -- Always runs at default edge location - -**`UNSUPPORTED_APPLICATION`** (rare, <1% of Workers) -- Smart Placement made Worker slower -- Placement decision reverted -- Always runs at edge location -- Won't be re-analyzed until redeployed - -## cf-placement Header (Beta) - -Smart Placement adds response header indicating routing decision: - -```typescript -// Remote placement (Smart Placement routed request) -"cf-placement: remote-LHR" // Routed to London - -// Local placement (default edge routing) -"cf-placement: local-EWR" // Stayed at Newark edge -``` - -Format: `{placement-type}-{IATA-code}` -- `remote-*` = Smart Placement routed to remote location -- `local-*` = Stayed at default edge location -- IATA code = nearest airport to data center - -**Warning:** Beta feature, may be removed before GA. - -## Detecting Smart Placement in Code - -**Note:** `cf-placement` header is a beta feature and may change or be removed. - -```typescript -export default { - async fetch(request: Request, env: Env): Promise { - const placementHeader = request.headers.get('cf-placement'); - - if (placementHeader?.startsWith('remote-')) { - const location = placementHeader.split('-')[1]; - console.log(`Smart Placement routed to ${location}`); - } else if (placementHeader?.startsWith('local-')) { - const location = placementHeader.split('-')[1]; - console.log(`Running at edge location ${location}`); - } - - return new Response('OK'); - } -} satisfies ExportedHandler; -``` - -## Request Duration Metrics - -Available in Cloudflare dashboard when Smart Placement enabled: - -**Workers & Pages → [Your Worker] → Metrics → Request Duration** - -Shows histogram comparing: -- Request duration WITH Smart Placement (99% of traffic) -- Request duration WITHOUT Smart Placement (1% baseline) - -**Request Duration vs Execution Duration:** -- **Request duration:** Total time from request arrival to response delivery (includes network latency) -- **Execution duration:** Time Worker code actively executing (excludes network waits) - -Use request duration to measure Smart Placement impact. - -### Interpreting Metrics - -| Metric Comparison | Interpretation | Action | -|-------------------|----------------|--------| -| WITH < WITHOUT | Smart Placement helping | Keep enabled | -| WITH ≈ WITHOUT | Neutral impact | Consider disabling to free resources | -| WITH > WITHOUT | Smart Placement hurting | Disable with `mode: "off"` | - -**Why Smart Placement might hurt performance:** -- Worker primarily serves static assets or cached content -- Backend services are globally distributed (no single optimal location) -- Worker has minimal backend communication -- Using Pages with `assets.run_worker_first = true` - -**Typical improvements when Smart Placement helps:** -- 20-50% reduction in request duration for database-heavy Workers -- 30-60% reduction for Workers making multiple backend API calls -- Larger improvements when backend is geographically concentrated - -## Monitoring Commands - -```bash -# Tail Worker logs -wrangler tail your-worker-name - -# Tail with filters -wrangler tail your-worker-name --status error -wrangler tail your-worker-name --header cf-placement - -# Check placement status via API -curl -H "Authorization: Bearer $TOKEN" \ - https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/services/$WORKER_NAME \ - | jq .result.placement_status -``` - -## TypeScript Types - -```typescript -// Placement status returned by API (field may be absent) -type PlacementStatus = - | 'SUCCESS' - | 'INSUFFICIENT_INVOCATIONS' - | 'UNSUPPORTED_APPLICATION' - | undefined; - -// Placement configuration in wrangler.jsonc -type PlacementMode = 'smart' | 'off'; - -interface PlacementConfig { - mode: PlacementMode; - // Legacy fields (deprecated/removed): - // hint?: string; // REMOVED - no longer supported -} - -// Explicit placement (separate feature from Smart Placement) -interface ExplicitPlacementConfig { - region?: string; - host?: string; - hostname?: string; - // Cannot combine with mode field -} - -// Worker metadata from API response -interface WorkerMetadata { - placement?: PlacementConfig | ExplicitPlacementConfig; - placement_status?: PlacementStatus; -} - -// Service Binding for backend Worker -interface Env { - BACKEND_SERVICE: Fetcher; // Service Binding to backend Worker - DATABASE: D1Database; -} - -// Example Worker with Service Binding -export default { - async fetch(request: Request, env: Env): Promise { - // Forward to backend Worker with Smart Placement enabled - const response = await env.BACKEND_SERVICE.fetch(request); - return response; - } -} satisfies ExportedHandler; -``` diff --git a/external/develop/devops/cloudflare/references/smart-placement/configuration.md b/external/develop/devops/cloudflare/references/smart-placement/configuration.md deleted file mode 100644 index 4f506ac..0000000 --- a/external/develop/devops/cloudflare/references/smart-placement/configuration.md +++ /dev/null @@ -1,196 +0,0 @@ -# Smart Placement Configuration - -## wrangler.jsonc Setup - -```jsonc -{ - "$schema": "./node_modules/wrangler/config-schema.json", - "placement": { - "mode": "smart" - } -} -``` - -## Placement Mode Values - -| Mode | Behavior | -|------|----------| -| `"smart"` | Enable Smart Placement - automatic optimization based on traffic analysis | -| `"off"` | Explicitly disable Smart Placement - always run at edge closest to user | -| Not specified | Default behavior - run at edge closest to user (same as `"off"`) | - -**Note:** Smart Placement vs Explicit Placement are separate features. Smart Placement (`mode: "smart"`) uses automatic analysis. For manual placement control, see explicit placement options (`region`, `host`, `hostname` fields - not covered in this reference). - -## Frontend + Backend Split Configuration - -### Frontend Worker (No Smart Placement) - -```jsonc -// frontend-worker/wrangler.jsonc -{ - "name": "frontend", - "main": "frontend-worker.ts", - // No "placement" - runs at edge - "services": [ - { - "binding": "BACKEND", - "service": "backend-api" - } - ] -} -``` - -### Backend Worker (Smart Placement Enabled) - -```jsonc -// backend-api/wrangler.jsonc -{ - "name": "backend-api", - "main": "backend-worker.ts", - "placement": { - "mode": "smart" - }, - "d1_databases": [ - { - "binding": "DATABASE", - "database_id": "xxx" - } - ] -} -``` - -## Requirements & Limitations - -### Requirements -- **Wrangler version:** 2.20.0+ -- **Analysis time:** Up to 15 minutes -- **Traffic requirements:** Consistent multi-location traffic -- **Workers plan:** All plans (Free, Paid, Enterprise) - -### What Smart Placement Affects - -**CRITICAL LIMITATION - Smart Placement ONLY Affects `fetch` Handlers:** - -Smart Placement is fundamentally limited to Workers with default `fetch` handlers. This is a key architectural constraint. - -- ✅ **Affects:** `fetch` event handlers ONLY (the default export's fetch method) -- ❌ **Does NOT affect:** - - RPC methods (Service Bindings with `WorkerEntrypoint` - see example below) - - Named entrypoints (exports other than `default`) - - Workers without `fetch` handlers - - Queue consumers, scheduled handlers, or other event types - -**Example - Smart Placement ONLY affects `fetch`:** -```typescript -// ✅ Smart Placement affects this: -export default { - async fetch(request: Request, env: Env): Promise { - // This runs close to backend when Smart Placement enabled - const data = await env.DATABASE.prepare('SELECT * FROM users').all(); - return Response.json(data); - } -} - -// ❌ Smart Placement DOES NOT affect these: -export class MyRPC extends WorkerEntrypoint { - async myMethod() { - // This ALWAYS runs at edge, Smart Placement has NO EFFECT - const data = await this.env.DATABASE.prepare('SELECT * FROM users').all(); - return data; - } -} - -export async function scheduled(event: ScheduledEvent, env: Env) { - // NOT affected by Smart Placement -} -``` - -**Consequence:** If your backend logic uses RPC methods (`WorkerEntrypoint`), Smart Placement cannot optimize those calls. You must use fetch-based patterns for Smart Placement to work. - -**Solution:** Convert RPC methods to fetch endpoints, or use a wrapper Worker with `fetch` handler that calls your backend RPC (though this adds latency). - -### Baseline Traffic -Smart Placement automatically routes 1% of requests WITHOUT optimization as baseline for performance comparison. - -### Validation Rules - -**Mutually exclusive fields:** -- `mode` cannot be used with explicit placement fields (`region`, `host`, `hostname`) -- Choose either Smart Placement OR explicit placement, not both - -```jsonc -// ✅ Valid - Smart Placement -{ "placement": { "mode": "smart" } } - -// ✅ Valid - Explicit Placement (different feature) -{ "placement": { "region": "us-east1" } } - -// ❌ Invalid - Cannot combine -{ "placement": { "mode": "smart", "region": "us-east1" } } -``` - -## Dashboard Configuration - -**Workers & Pages** → Select Worker → **Settings** → **General** → **Placement: Smart** → Wait 15min → Check **Metrics** - -## TypeScript Types - -```typescript -interface Env { - BACKEND: Fetcher; - DATABASE: D1Database; -} - -export default { - async fetch(request: Request, env: Env): Promise { - const data = await env.DATABASE.prepare('SELECT * FROM table').all(); - return Response.json(data); - } -} satisfies ExportedHandler; -``` - -## Cloudflare Pages/Assets Warning - -**CRITICAL PERFORMANCE ISSUE:** Enabling Smart Placement with `assets.run_worker_first = true` in Pages projects **severely degrades asset serving performance**. This is one of the most common misconfigurations. - -**Why this is bad:** -- Smart Placement routes ALL requests (including static assets) away from edge to remote locations -- Static assets (HTML, CSS, JS, images) should ALWAYS be served from edge closest to user -- Result: 2-5x slower asset loading times, poor user experience - -**Problem:** Smart Placement routes asset requests away from edge, but static assets should always be served from edge closest to user. - -**Solutions (in order of preference):** -1. **Recommended:** Split into separate Workers (frontend at edge + backend with Smart Placement) -2. Set `"mode": "off"` to explicitly disable Smart Placement for Pages/Assets Workers -3. Use `assets.run_worker_first = false` (serves assets first, bypasses Worker for static content) - -```jsonc -// ❌ BAD - Degrades asset performance by 2-5x -{ - "name": "pages-app", - "placement": { "mode": "smart" }, - "assets": { "run_worker_first": true } -} - -// ✅ GOOD - Frontend at edge, backend optimized -// frontend-worker/wrangler.jsonc -{ - "name": "frontend", - "assets": { "run_worker_first": true } - // No placement - runs at edge -} - -// backend-worker/wrangler.jsonc -{ - "name": "backend-api", - "placement": { "mode": "smart" }, - "d1_databases": [{ "binding": "DB", "database_id": "xxx" }] -} -``` - -**Key takeaway:** Never enable Smart Placement on Workers that serve static assets with `run_worker_first = true`. - -## Local Development - -Smart Placement does NOT work in `wrangler dev` (local only). Test by deploying: `wrangler deploy --env staging` diff --git a/external/develop/devops/cloudflare/references/smart-placement/gotchas.md b/external/develop/devops/cloudflare/references/smart-placement/gotchas.md deleted file mode 100644 index dc94e9b..0000000 --- a/external/develop/devops/cloudflare/references/smart-placement/gotchas.md +++ /dev/null @@ -1,174 +0,0 @@ -# Smart Placement Gotchas - -## Common Errors - -### "INSUFFICIENT_INVOCATIONS" - -**Cause:** Not enough traffic for Smart Placement to analyze -**Solution:** -- Ensure Worker receives consistent global traffic -- Wait longer (analysis takes up to 15 minutes) -- Send test traffic from multiple global locations -- Check Worker has fetch event handler - -### "UNSUPPORTED_APPLICATION" - -**Cause:** Smart Placement made Worker slower rather than faster -**Reasons:** -- Worker doesn't make backend calls (runs faster at edge) -- Backend calls are cached (network latency to user more important) -- Backend service has good global distribution -- Worker serves static assets or Pages content - -**Solutions:** -- Disable Smart Placement: `{ "placement": { "mode": "off" } }` -- Review whether Worker actually benefits from Smart Placement -- Consider caching strategy to reduce backend calls -- For Pages/Assets Workers, use separate backend Worker with Smart Placement - -### "No request duration metrics" - -**Cause:** Smart Placement not enabled, insufficient time passed, insufficient traffic, or analysis incomplete -**Solution:** -- Ensure Smart Placement enabled in config -- Wait 15+ minutes after deployment -- Verify Worker has sufficient traffic -- Check `placement_status` is `SUCCESS` - -### "cf-placement header missing" - -**Cause:** Smart Placement not enabled, beta feature removed, or Worker not analyzed yet -**Solution:** Verify Smart Placement enabled, wait for analysis (15min), check if beta feature still available - -## Pages/Assets + Smart Placement Performance Degradation - -**Problem:** Static assets load 2-5x slower when Smart Placement enabled with `run_worker_first = true`. - -**Cause:** Smart Placement routes ALL requests (including static assets like HTML, CSS, JS, images) to remote locations. Static content should ALWAYS be served from edge closest to user. - -**Solution:** Split into separate Workers OR disable Smart Placement: -```jsonc -// ❌ BAD - Assets routed away from user -{ - "name": "pages-app", - "placement": { "mode": "smart" }, - "assets": { "run_worker_first": true } -} - -// ✅ GOOD - Assets at edge, API optimized -// frontend/wrangler.jsonc -{ - "name": "frontend", - "assets": { "run_worker_first": true } - // No placement field - stays at edge -} - -// backend/wrangler.jsonc -{ - "name": "backend-api", - "placement": { "mode": "smart" } -} -``` - -This is one of the most common and impactful Smart Placement misconfigurations. - -## Monolithic Full-Stack Worker - -**Problem:** Frontend and backend logic in single Worker with Smart Placement enabled. - -**Cause:** Smart Placement optimizes for backend latency but increases user-facing response time. - -**Solution:** Split into two Workers: -```jsonc -// frontend/wrangler.jsonc -{ - "name": "frontend", - "placement": { "mode": "off" }, // Explicit: stay at edge - "services": [{ "binding": "BACKEND", "service": "backend-api" }] -} - -// backend/wrangler.jsonc -{ - "name": "backend-api", - "placement": { "mode": "smart" }, - "d1_databases": [{ "binding": "DB", "database_id": "xxx" }] -} -``` - -## Local Development Confusion - -**Issue:** Smart Placement doesn't work in `wrangler dev`. - -**Explanation:** Smart Placement only activates in production deployments, not local development. - -**Solution:** Test Smart Placement in staging environment: `wrangler deploy --env staging` - -## Baseline Traffic & Analysis Time - -**Note:** Smart Placement routes 1% of requests WITHOUT optimization for comparison (expected). - -**Analysis time:** Up to 15 minutes. During analysis, Worker runs at edge. Monitor `placement_status`. - -## RPC Methods Not Affected (Critical Limitation) - -**Problem:** Enabled Smart Placement on backend but RPC calls still slow. - -**Cause:** Smart Placement ONLY affects `fetch` handlers. RPC methods (Service Bindings with `WorkerEntrypoint`) are NEVER affected. - -**Why:** RPC bypasses `fetch` handler - Smart Placement can only route `fetch` requests. - -**Solution:** Convert to fetch-based Service Bindings: - -```typescript -// ❌ RPC - Smart Placement has NO EFFECT -export class BackendRPC extends WorkerEntrypoint { - async getData() { - // ALWAYS runs at edge - return await this.env.DATABASE.prepare('SELECT * FROM table').all(); - } -} - -// ✅ Fetch - Smart Placement WORKS -export default { - async fetch(request: Request, env: Env): Promise { - // Runs close to DATABASE when Smart Placement enabled - const data = await env.DATABASE.prepare('SELECT * FROM table').all(); - return Response.json(data); - } -} -``` - -## Requirements - -- **Wrangler 2.20.0+** required -- **Consistent multi-region traffic** needed for analysis -- **Only affects fetch handlers** - RPC methods and named entrypoints not affected - -## Limits - -| Resource/Limit | Value | Notes | -|----------------|-------|-------| -| Analysis time | Up to 15 minutes | After enabling | -| Baseline traffic | 1% | Routed without optimization | -| Min Wrangler version | 2.20.0+ | Required | -| Traffic requirement | Multi-region | Consistent needed | - -## Disabling Smart Placement - -```jsonc -{ "placement": { "mode": "off" } } // Explicit disable -// OR remove "placement" field entirely (same effect) -``` - -Both behaviors identical - Worker runs at edge closest to user. - -## When NOT to Use Smart Placement - -- Workers serving only static content or cached responses -- Workers without significant backend communication -- Pure edge logic (auth checks, redirects, simple transformations) -- Workers without fetch event handlers -- Pages/Assets Workers with `run_worker_first = true` -- Workers using RPC methods instead of fetch handlers - -These scenarios won't benefit and may perform worse with Smart Placement. diff --git a/external/develop/devops/cloudflare/references/smart-placement/patterns.md b/external/develop/devops/cloudflare/references/smart-placement/patterns.md deleted file mode 100644 index 40dc4dd..0000000 --- a/external/develop/devops/cloudflare/references/smart-placement/patterns.md +++ /dev/null @@ -1,183 +0,0 @@ -# Smart Placement Patterns - -## Backend Worker with Database Access - -```typescript -export default { - async fetch(request: Request, env: Env): Promise { - const user = await env.DATABASE.prepare('SELECT * FROM users WHERE id = ?').bind(userId).first(); - const orders = await env.DATABASE.prepare('SELECT * FROM orders WHERE user_id = ?').bind(userId).all(); - return Response.json({ user, orders }); - } -}; -``` - -```jsonc -{ "placement": { "mode": "smart" }, "d1_databases": [{ "binding": "DATABASE", "database_id": "xxx" }] } -``` - -## Frontend + Backend Split (Service Bindings) - -**Frontend:** Runs at edge for fast user response -**Backend:** Smart Placement runs close to database - -```typescript -// Frontend Worker - routes requests to backend -interface Env { - BACKEND: Fetcher; // Service Binding to backend Worker -} - -export default { - async fetch(request: Request, env: Env): Promise { - if (new URL(request.url).pathname.startsWith('/api/')) { - return env.BACKEND.fetch(request); // Forward to backend - } - return new Response('Frontend content'); - } -}; - -// Backend Worker - database operations -interface BackendEnv { - DATABASE: D1Database; -} - -export default { - async fetch(request: Request, env: BackendEnv): Promise { - const data = await env.DATABASE.prepare('SELECT * FROM table').all(); - return Response.json(data); - } -}; -``` - -**CRITICAL:** Use fetch-based Service Bindings (shown above). If using RPC with `WorkerEntrypoint`, Smart Placement will NOT optimize those method calls - only `fetch` handlers are affected. - -**RPC vs Fetch - CRITICAL:** Smart Placement ONLY works with fetch-based bindings, NOT RPC. - -```typescript -// ❌ RPC - Smart Placement has NO EFFECT on backend RPC methods -export class BackendRPC extends WorkerEntrypoint { - async getData() { - // ALWAYS runs at edge, Smart Placement ignored - return await this.env.DATABASE.prepare('SELECT * FROM table').all(); - } -} - -// ✅ Fetch - Smart Placement WORKS -export default { - async fetch(request: Request, env: Env): Promise { - // Runs close to DATABASE when Smart Placement enabled - const data = await env.DATABASE.prepare('SELECT * FROM table').all(); - return Response.json(data); - } -}; -``` - -## External API Integration - -```typescript -export default { - async fetch(request: Request, env: Env): Promise { - const apiUrl = 'https://api.partner.com'; - const headers = { 'Authorization': `Bearer ${env.API_KEY}` }; - - const [profile, transactions] = await Promise.all([ - fetch(`${apiUrl}/profile`, { headers }), - fetch(`${apiUrl}/transactions`, { headers }) - ]); - - return Response.json({ - profile: await profile.json(), - transactions: await transactions.json() - }); - } -}; -``` - -## SSR / API Gateway Pattern - -```typescript -// Frontend (edge) - auth/routing close to user -export default { - async fetch(request: Request, env: Env) { - if (!request.headers.get('Authorization')) { - return new Response('Unauthorized', { status: 401 }); - } - const data = await env.BACKEND.fetch(request); - return new Response(renderPage(await data.json()), { - headers: { 'Content-Type': 'text/html' } - }); - } -}; - -// Backend (Smart Placement) - DB operations close to data -export default { - async fetch(request: Request, env: Env) { - const data = await env.DATABASE.prepare('SELECT * FROM pages WHERE id = ?').bind(pageId).first(); - return Response.json(data); - } -}; -``` - -## Durable Objects with Smart Placement - -**Key principle:** Smart Placement does NOT control WHERE Durable Objects run. DOs always run in their designated region (based on jurisdiction or smart location hints). - -**What Smart Placement DOES affect:** The location of the coordinator Worker's `fetch` handler that makes calls to multiple DOs. - -**Pattern:** Enable Smart Placement on coordinator Worker that aggregates data from multiple DOs: - -```typescript -// Worker with Smart Placement - aggregates data from multiple DOs -export default { - async fetch(request: Request, env: Env): Promise { - const userId = new URL(request.url).searchParams.get('user'); - - // Get DO stubs - const userDO = env.USER_DO.get(env.USER_DO.idFromName(userId)); - const analyticsID = env.ANALYTICS_DO.idFromName(`analytics-${userId}`); - const analyticsDO = env.ANALYTICS_DO.get(analyticsID); - - // Fetch from multiple DOs - const [userData, analyticsData] = await Promise.all([ - userDO.fetch(new Request('https://do/profile')), - analyticsDO.fetch(new Request('https://do/stats')) - ]); - - return Response.json({ - user: await userData.json(), - analytics: await analyticsData.json() - }); - } -}; -``` - -```jsonc -// wrangler.jsonc -{ - "placement": { "mode": "smart" }, - "durable_objects": { - "bindings": [ - { "name": "USER_DO", "class_name": "UserDO" }, - { "name": "ANALYTICS_DO", "class_name": "AnalyticsDO" } - ] - } -} -``` - -**When this helps:** -- Worker's `fetch` handler runs closer to DO regions, reducing network latency for multiple DO calls -- Most beneficial when DOs are geographically concentrated or in specific jurisdictions -- Helps when coordinator makes many sequential or parallel DO calls - -**When this DOESN'T help:** -- DOs are globally distributed (no single optimal Worker location) -- Worker only calls a single DO -- DO calls are infrequent or cached - -## Best Practices - -- Split full-stack apps: frontend at edge, backend with Smart Placement -- Use fetch-based Service Bindings (not RPC) -- Enable for backend logic: APIs, data aggregation, DB operations -- Don't enable for: static content, edge logic, RPC methods, Pages with `run_worker_first` -- Wait 15+ min for analysis, verify `placement_status = SUCCESS` diff --git a/external/develop/devops/cloudflare/references/snippets/README.md b/external/develop/devops/cloudflare/references/snippets/README.md deleted file mode 100644 index a09a1c4..0000000 --- a/external/develop/devops/cloudflare/references/snippets/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# Cloudflare Snippets Skill Reference - -## Description -Expert guidance for **Cloudflare Snippets ONLY** - a lightweight JavaScript-based edge logic platform for modifying HTTP requests and responses. Snippets run as part of the Ruleset Engine and are included at no additional cost on paid plans (Pro, Business, Enterprise). - -## What Are Snippets? -Snippets are JavaScript functions executed at the edge as part of Cloudflare's Ruleset Engine. Key characteristics: -- **Execution time**: 5ms CPU limit per request -- **Size limit**: 32KB per snippet -- **Runtime**: V8 isolate (subset of Workers APIs) -- **Subrequests**: 2-5 fetch calls depending on plan -- **Cost**: Included with Pro/Business/Enterprise plans - -## Snippets vs Workers Decision Matrix - -| Factor | Choose Snippets If... | Choose Workers If... | -|--------|----------------------|---------------------| -| **Complexity** | Simple request/response modifications | Complex business logic, routing, middleware | -| **Execution time** | <5ms sufficient | Need >5ms or variable time | -| **Subrequests** | 2-5 fetch calls sufficient | Need >5 subrequests or complex orchestration | -| **Code size** | <32KB sufficient | Need >32KB or npm dependencies | -| **Cost** | Want zero additional cost | Can afford $5/mo + usage | -| **APIs** | Need basic fetch, headers, URL | Need KV, D1, R2, Durable Objects, cron triggers | -| **Deployment** | Need rule-based triggers | Want custom routing logic | - -**Rule of thumb**: Use Snippets for modifications, Workers for applications. - -## Execution Model -1. Request arrives at Cloudflare edge -2. Ruleset Engine evaluates snippet rules (filter expressions) -3. If rule matches, snippet executes within 5ms limit -4. Modified request/response continues through pipeline -5. Response returned to client - -Snippets execute synchronously in the request path - performance is critical. - -## Reading Order -1. **[configuration.md](configuration.md)** - Start here: setup, deployment methods (Dashboard/API/Terraform) -2. **[api.md](api.md)** - Core APIs: Request, Response, headers, `request.cf` properties -3. **[patterns.md](patterns.md)** - Real-world examples: geo-routing, A/B tests, security headers -4. **[gotchas.md](gotchas.md)** - Troubleshooting: common errors, performance tips, API limitations - -## In This Reference - -- **[configuration.md](configuration.md)** - Setup, deployment, configuration -- **[api.md](api.md)** - API endpoints, methods, interfaces -- **[patterns.md](patterns.md)** - Common patterns, use cases, examples -- **[gotchas.md](gotchas.md)** - Troubleshooting, best practices, limitations - -## Quick Start -```javascript -// Snippet: Add security headers -export default { - async fetch(request) { - const response = await fetch(request); - const newResponse = new Response(response.body, response); - newResponse.headers.set("X-Frame-Options", "DENY"); - newResponse.headers.set("X-Content-Type-Options", "nosniff"); - return newResponse; - } -} -``` - -Deploy via Dashboard (Rules → Snippets) or API/Terraform. See configuration.md for details. - -## See Also - -- [Cloudflare Docs](https://developers.cloudflare.com/rules/snippets/) diff --git a/external/develop/devops/cloudflare/references/snippets/api.md b/external/develop/devops/cloudflare/references/snippets/api.md deleted file mode 100644 index 76a5a4b..0000000 --- a/external/develop/devops/cloudflare/references/snippets/api.md +++ /dev/null @@ -1,198 +0,0 @@ -# Snippets API Reference - -## Request Object - -### HTTP Properties -```javascript -request.method // GET, POST, PUT, DELETE, etc. -request.url // Full URL string -request.headers // Headers object -request.body // ReadableStream (for POST/PUT) -request.cf // Cloudflare properties (see below) -``` - -### URL Operations -```javascript -const url = new URL(request.url); -url.hostname // "example.com" -url.pathname // "/path/to/page" -url.search // "?query=value" -url.searchParams.get("q") // "value" -url.searchParams.set("q", "new") -url.searchParams.delete("q") -``` - -### Header Operations -```javascript -// Read headers -request.headers.get("User-Agent") -request.headers.has("Authorization") -request.headers.getSetCookie() // Get all Set-Cookie headers - -// Modify headers (create new request) -const modifiedRequest = new Request(request); -modifiedRequest.headers.set("X-Custom", "value") -modifiedRequest.headers.delete("X-Remove") -``` - -### Cloudflare Properties (`request.cf`) -Access Cloudflare-specific metadata about the request: - -```javascript -// Geolocation -request.cf.city // "San Francisco" -request.cf.continent // "NA" -request.cf.country // "US" -request.cf.region // "California" or "CA" -request.cf.regionCode // "CA" -request.cf.postalCode // "94102" -request.cf.latitude // "37.7749" -request.cf.longitude // "-122.4194" -request.cf.timezone // "America/Los_Angeles" -request.cf.metroCode // "807" (DMA code) - -// Network -request.cf.colo // "SFO" (airport code of datacenter) -request.cf.asn // 13335 (ASN number) -request.cf.asOrganization // "Cloudflare, Inc." - -// Bot Management (if enabled) -request.cf.botManagement.score // 1-99 (1=bot, 99=human) -request.cf.botManagement.verified_bot // true/false -request.cf.botManagement.static_resource // true/false - -// TLS/HTTP version -request.cf.tlsVersion // "TLSv1.3" -request.cf.tlsCipher // "AEAD-AES128-GCM-SHA256" -request.cf.httpProtocol // "HTTP/2" - -// Request metadata -request.cf.requestPriority // "weight=192;exclusive=0" -``` - -**Use cases**: Geo-routing, bot detection, security decisions, analytics. - -## Response Object - -### Response Constructors -```javascript -// Plain text -new Response("Hello", { status: 200 }) - -// JSON -Response.json({ key: "value" }, { status: 200 }) - -// HTML -new Response("

Hi

", { - status: 200, - headers: { "Content-Type": "text/html" } -}) - -// Redirect -Response.redirect("https://example.com", 301) // or 302 - -// Stream (pass through) -new Response(response.body, response) -``` - -### Response Headers -```javascript -// Create modified response -const newResponse = new Response(response.body, response); - -// Set/modify headers -newResponse.headers.set("X-Custom", "value") -newResponse.headers.append("Set-Cookie", "session=abc; Path=/") -newResponse.headers.delete("Server") - -// Common headers -newResponse.headers.set("Cache-Control", "public, max-age=3600") -newResponse.headers.set("Content-Type", "application/json") -``` - -### Response Properties -```javascript -response.status // 200, 404, 500, etc. -response.statusText // "OK", "Not Found", etc. -response.headers // Headers object -response.body // ReadableStream -response.ok // true if status 200-299 -response.redirected // true if redirected -``` - -## REST API Operations - -### List Snippets -```bash -GET /zones/{zone_id}/snippets -``` - -### Get Snippet -```bash -GET /zones/{zone_id}/snippets/{snippet_name} -``` - -### Create/Update Snippet -```bash -PUT /zones/{zone_id}/snippets/{snippet_name} -Content-Type: multipart/form-data - -files=@snippet.js -metadata={"main_module":"snippet.js"} -``` - -### Delete Snippet -```bash -DELETE /zones/{zone_id}/snippets/{snippet_name} -``` - -### List Snippet Rules -```bash -GET /zones/{zone_id}/rulesets/phases/http_request_snippets/entrypoint -``` - -### Update Snippet Rules -```bash -PUT /zones/{zone_id}/snippets/snippet_rules -Content-Type: application/json - -{ - "rules": [{ - "description": "Apply snippet", - "enabled": true, - "expression": "http.host eq \"example.com\"", - "snippet_name": "my_snippet" - }] -} -``` - -## Available APIs in Snippets - -### ✅ Supported -- `fetch()` - HTTP requests (2-5 subrequests per plan) -- `Request` / `Response` - Standard Web APIs -- `URL` / `URLSearchParams` - URL manipulation -- `Headers` - Header manipulation -- `TextEncoder` / `TextDecoder` - Text encoding -- `crypto.subtle` - Web Crypto API (hashing, signing) -- `crypto.randomUUID()` - UUID generation - -### ❌ Not Supported in Snippets -- `caches` API - Not available (use Workers) -- `KV`, `D1`, `R2` - Storage APIs (use Workers) -- `Durable Objects` - Stateful objects (use Workers) -- `WebSocket` - WebSocket upgrades (use Workers) -- `HTMLRewriter` - HTML parsing (use Workers) -- `import` statements - No module imports -- `addEventListener` - Use `export default { async fetch() {}` pattern - -## Snippet Structure -```javascript -export default { - async fetch(request) { - // Your logic here - const response = await fetch(request); - return response; // or modified response - } -} -``` \ No newline at end of file diff --git a/external/develop/devops/cloudflare/references/snippets/configuration.md b/external/develop/devops/cloudflare/references/snippets/configuration.md deleted file mode 100644 index b5bea0f..0000000 --- a/external/develop/devops/cloudflare/references/snippets/configuration.md +++ /dev/null @@ -1,227 +0,0 @@ -# Snippets Configuration Guide - -## Configuration Methods - -### 1. Dashboard (GUI) -**Best for**: Quick tests, single snippets, visual rule building - -``` -1. Go to zone → Rules → Snippets -2. Click "Create Snippet" or select template -3. Enter snippet name (a-z, 0-9, _ only, cannot change later) -4. Write JavaScript code (32KB max) -5. Configure snippet rule: - - Expression Builder (visual) or Expression Editor (text) - - Use Ruleset Engine filter expressions -6. Test with Preview/HTTP tabs -7. Deploy or Save as Draft -``` - -### 2. REST API -**Best for**: CI/CD, automation, programmatic management - -```bash -# Create/update snippet -curl "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/snippets/$SNIPPET_NAME" \ - --request PUT \ - --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - --form "files=@example.js" \ - --form "metadata={\"main_module\": \"example.js\"}" - -# Create snippet rule -curl "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/snippets/snippet_rules" \ - --request PUT \ - --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - --header "Content-Type: application/json" \ - --data '{ - "rules": [ - { - "description": "Trigger snippet on /api paths", - "enabled": true, - "expression": "starts_with(http.request.uri.path, \"/api/\")", - "snippet_name": "api_snippet" - } - ] - }' - -# List snippets -curl "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/snippets" \ - --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" - -# Delete snippet -curl "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/snippets/$SNIPPET_NAME" \ - --request DELETE \ - --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -``` - -### 3. Terraform -**Best for**: Infrastructure-as-code, multi-zone deployments - -```hcl -# Configure Terraform provider -terraform { - required_providers { - cloudflare = { - source = "cloudflare/cloudflare" - version = "~> 4.0" - } - } -} - -provider "cloudflare" { - api_token = var.cloudflare_api_token -} - -# Create snippet -resource "cloudflare_snippet" "security_headers" { - zone_id = var.zone_id - name = "security_headers" - - main_module = "security_headers.js" - files { - name = "security_headers.js" - content = file("${path.module}/snippets/security_headers.js") - } -} - -# Create snippet rule -resource "cloudflare_snippet_rules" "security_rules" { - zone_id = var.zone_id - - rules { - description = "Apply security headers to all requests" - enabled = true - expression = "true" - snippet_name = cloudflare_snippet.security_headers.name - } -} -``` - -### 4. Pulumi -**Best for**: Multi-cloud IaC, TypeScript/Python/Go workflows - -```typescript -import * as cloudflare from "@pulumi/cloudflare"; -import * as fs from "fs"; - -// Create snippet -const securitySnippet = new cloudflare.Snippet("security-headers", { - zoneId: zoneId, - name: "security_headers", - mainModule: "security_headers.js", - files: [{ - name: "security_headers.js", - content: fs.readFileSync("./snippets/security_headers.js", "utf8"), - }], -}); - -// Create snippet rule -const snippetRule = new cloudflare.SnippetRules("security-rules", { - zoneId: zoneId, - rules: [{ - description: "Apply security headers", - enabled: true, - expression: "true", - snippetName: securitySnippet.name, - }], -}); -``` - -## Filter Expressions - -Snippets use Cloudflare's Ruleset Engine expression language to determine when to execute. - -### Common Expression Patterns - -```javascript -// Host matching -http.host eq "example.com" -http.host in {"example.com" "www.example.com"} -http.host contains "example" - -// Path matching -http.request.uri.path eq "/api/users" -starts_with(http.request.uri.path, "/api/") -ends_with(http.request.uri.path, ".json") -matches(http.request.uri.path, "^/api/v[0-9]+/") - -// Query parameters -http.request.uri.query contains "debug=true" - -// Headers -http.headers["user-agent"] contains "Mobile" -http.headers["accept-language"] eq "en-US" - -// Cookies -http.cookie contains "session=" - -// Geolocation -ip.geoip.country eq "US" -ip.geoip.continent eq "EU" - -// Bot detection (requires Bot Management) -cf.bot_management.score lt 30 - -// Method -http.request.method eq "POST" -http.request.method in {"POST" "PUT" "PATCH"} - -// Combine with logical operators -http.host eq "example.com" and starts_with(http.request.uri.path, "/api/") -ip.geoip.country eq "US" or ip.geoip.country eq "CA" -not http.headers["user-agent"] contains "bot" -``` - -### Expression Functions - -| Function | Example | Description | -|----------|---------|-------------| -| `starts_with()` | `starts_with(http.request.uri.path, "/api/")` | Check prefix | -| `ends_with()` | `ends_with(http.request.uri.path, ".json")` | Check suffix | -| `contains()` | `contains(http.headers["user-agent"], "Mobile")` | Check substring | -| `matches()` | `matches(http.request.uri.path, "^/api/")` | Regex match | -| `lower()` | `lower(http.host) eq "example.com"` | Convert to lowercase | -| `upper()` | `upper(http.headers["x-api-key"])` | Convert to uppercase | -| `len()` | `len(http.request.uri.path) gt 100` | String length | - -## Deployment Workflow - -### Development -1. Write snippet code locally -2. Test syntax with `node snippet.js` or TypeScript compiler -3. Deploy to Dashboard or use API with `Save as Draft` -4. Test with Preview/HTTP tabs in Dashboard -5. Enable rule when ready - -### Production -1. Store snippet code in version control -2. Use Terraform/Pulumi for reproducible deployments -3. Deploy to staging zone first -4. Test with real traffic (use low-traffic subdomain) -5. Apply to production zone -6. Monitor with Analytics/Logpush - -## Limits & Requirements - -| Resource | Limit | Notes | -|----------|-------|-------| -| Snippet size | 32 KB | Per snippet, compressed | -| Snippet name | 64 chars | `a-z`, `0-9`, `_` only, immutable | -| Snippets per zone | 20 | Soft limit, contact support for more | -| Rules per zone | 20 | One rule per snippet typical | -| Expression length | 4096 chars | Per rule expression | - -## Authentication - -### API Token (Recommended) -```bash -# Create token at: https://dash.cloudflare.com/profile/api-tokens -# Required permissions: Zone.Snippets:Edit, Zone.Rules:Edit -export CLOUDFLARE_API_TOKEN="your_token_here" -``` - -### API Key (Legacy) -```bash -export CLOUDFLARE_EMAIL="your@email.com" -export CLOUDFLARE_API_KEY="your_global_api_key" -``` \ No newline at end of file diff --git a/external/develop/devops/cloudflare/references/snippets/gotchas.md b/external/develop/devops/cloudflare/references/snippets/gotchas.md deleted file mode 100644 index 832077e..0000000 --- a/external/develop/devops/cloudflare/references/snippets/gotchas.md +++ /dev/null @@ -1,86 +0,0 @@ -# Gotchas & Best Practices - -## Common Errors - -### 1000: "Snippet execution failed" -Runtime error or syntax error. Wrap code in try/catch: -```javascript -try { return await fetch(request); } -catch (error) { return new Response(`Error: ${error.message}`, { status: 500 }); } -``` - -### 1100: "Exceeded execution limit" -Code takes >5ms CPU. Simplify logic or move to Workers. - -### 1201: "Multiple origin fetches" -Call `fetch(request)` exactly once: -```javascript -// ❌ Multiple origin fetches -const r1 = await fetch(request); const r2 = await fetch(request); -// ✅ Single fetch, reuse response -const response = await fetch(request); -``` - -### 1202: "Subrequest limit exceeded" -Pro: 2 subrequests, Business/Enterprise: 5. Reduce fetch calls. - -### "Cannot set property on immutable object" -Clone before modifying: -```javascript -const modifiedRequest = new Request(request); -modifiedRequest.headers.set("X-Custom", "value"); -``` - -### "caches is not defined" -Cache API NOT available in Snippets. Use Workers. - -### "Module not found" -Snippets don't support `import`. Use inline code or Workers. - -## Best Practices - -### Performance -- Keep code <10KB (32KB limit) -- Optimize for 5ms CPU -- Clone only when modifying -- Minimize subrequests - -### Security -- Validate all inputs -- Use Web Crypto API for hashing -- Sanitize headers before origin -- Don't log secrets - -### Debugging -```javascript -newResponse.headers.set("X-Debug-Country", request.cf.country); -``` -```bash -curl -H "X-Test: true" https://example.com -v -``` - -## Available APIs - -**✅ Available:** `fetch()`, `Request`, `Response`, `Headers`, `URL`, `crypto.subtle`, `crypto.randomUUID()`, `atob()`/`btoa()`, `JSON` - -**❌ NOT Available:** `caches`, `KV`, `D1`, `R2`, `Durable Objects`, `WebSocket`, `HTMLRewriter`, `import`, Node.js APIs - -## Limits - -| Resource | Limit | -|----------|-------| -| Snippet size | 32KB | -| Execution time | 5ms CPU | -| Subrequests (Pro/Biz) | 2/5 | -| Snippets/zone | 20 | - -## Performance Benchmarks - -| Operation | Time | -|-----------|------| -| Header set | <0.1ms | -| URL parsing | <0.2ms | -| fetch() | 1-3ms | -| SHA-256 | 0.5-1ms | - -**Migrate to Workers when:** >5ms needed, >5 subrequests, need storage (KV/D1/R2), need npm packages, >32KB code diff --git a/external/develop/devops/cloudflare/references/snippets/patterns.md b/external/develop/devops/cloudflare/references/snippets/patterns.md deleted file mode 100644 index a60c420..0000000 --- a/external/develop/devops/cloudflare/references/snippets/patterns.md +++ /dev/null @@ -1,135 +0,0 @@ -# Snippets Patterns - -## Security Headers - -```javascript -export default { - async fetch(request) { - const response = await fetch(request); - const newResponse = new Response(response.body, response); - newResponse.headers.set("X-Frame-Options", "DENY"); - newResponse.headers.set("X-Content-Type-Options", "nosniff"); - newResponse.headers.delete("X-Powered-By"); - return newResponse; - } -} -``` - -**Rule:** `true` (all requests) - -## Geo-Based Routing - -```javascript -export default { - async fetch(request) { - const country = request.cf.country; - if (["GB", "DE", "FR"].includes(country)) { - const url = new URL(request.url); - url.hostname = url.hostname.replace(".com", ".eu"); - return Response.redirect(url.toString(), 302); - } - return fetch(request); - } -} -``` - -## A/B Testing - -```javascript -export default { - async fetch(request) { - const cookies = request.headers.get("Cookie") || ""; - let variant = cookies.match(/ab_test=([AB])/)?.[1] || (Math.random() < 0.5 ? "A" : "B"); - - const req = new Request(request); - req.headers.set("X-Variant", variant); - const response = await fetch(req); - - if (!cookies.includes("ab_test=")) { - const newResponse = new Response(response.body, response); - newResponse.headers.append("Set-Cookie", `ab_test=${variant}; Path=/; Secure`); - return newResponse; - } - return response; - } -} -``` - -## Bot Detection - -```javascript -export default { - async fetch(request) { - const botScore = request.cf.botManagement?.score; - if (botScore && botScore < 30) return new Response("Denied", { status: 403 }); - return fetch(request); - } -} -``` - -**Requires:** Bot Management plan - -## API Auth Header Injection - -```javascript -export default { - async fetch(request) { - if (new URL(request.url).pathname.startsWith("/api/")) { - const req = new Request(request); - req.headers.set("X-Internal-Auth", "secret_token"); - req.headers.delete("Authorization"); - return fetch(req); - } - return fetch(request); - } -} -``` - -## CORS Headers - -```javascript -export default { - async fetch(request) { - if (request.method === "OPTIONS") { - return new Response(null, { - status: 204, - headers: { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE", - "Access-Control-Allow-Headers": "Content-Type, Authorization" - } - }); - } - const response = await fetch(request); - const newResponse = new Response(response.body, response); - newResponse.headers.set("Access-Control-Allow-Origin", "*"); - return newResponse; - } -} -``` - -## Maintenance Mode - -```javascript -export default { - async fetch(request) { - if (request.headers.get("X-Bypass-Token") === "admin") return fetch(request); - return new Response("

Maintenance

", { - status: 503, - headers: { "Content-Type": "text/html", "Retry-After": "3600" } - }); - } -} -``` - -## Pattern Selection - -| Pattern | Complexity | Use Case | -|---------|-----------|----------| -| Security Headers | Low | All sites | -| Geo-Routing | Low | Regional content | -| A/B Testing | Medium | Experiments | -| Bot Detection | Medium | Requires Bot Management | -| API Auth | Low | Backend protection | -| CORS | Low | API endpoints | -| Maintenance | Low | Deployments | diff --git a/external/develop/devops/cloudflare/references/spectrum/README.md b/external/develop/devops/cloudflare/references/spectrum/README.md deleted file mode 100644 index f78350d..0000000 --- a/external/develop/devops/cloudflare/references/spectrum/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Cloudflare Spectrum Skill Reference - -## Overview - -Cloudflare Spectrum provides security and acceleration for ANY TCP or UDP-based application. It's a global Layer 4 (L4) reverse proxy running on Cloudflare's edge nodes that routes MQTT, email, file transfer, version control, games, and more through Cloudflare to mask origins and protect from DDoS attacks. - -**When to Use Spectrum**: When your protocol isn't HTTP/HTTPS (use Cloudflare proxy for HTTP). Spectrum handles everything else: SSH, gaming, databases, MQTT, SMTP, RDP, custom protocols. - -## Plan Capabilities - -| Capability | Pro/Business | Enterprise | -|------------|--------------|------------| -| TCP protocols | Selected ports only | All ports (1-65535) | -| UDP protocols | Selected ports only | All ports (1-65535) | -| Port ranges | ❌ | ✅ | -| Argo Smart Routing | ✅ | ✅ | -| IP Firewall | ✅ | ✅ | -| Load balancer origins | ✅ | ✅ | - -## Decision Tree - -**What are you trying to do?** - -1. **Create/manage Spectrum app** - - Via Dashboard → See [Cloudflare Dashboard](https://dash.cloudflare.com) - - Via API → See [api.md](api.md) - REST endpoints - - Via SDK → See [api.md](api.md) - TypeScript/Python/Go examples - - Via IaC → See [configuration.md](configuration.md) - Terraform/Pulumi - -2. **Protect specific protocol** - - SSH → See [patterns.md](patterns.md#1-ssh-server-protection) - - Gaming (Minecraft, etc) → See [patterns.md](patterns.md#2-game-server) - - MQTT/IoT → See [patterns.md](patterns.md#3-mqtt-broker) - - SMTP/Email → See [patterns.md](patterns.md#4-smtp-relay) - - Database → See [patterns.md](patterns.md#5-database-proxy) - - RDP → See [patterns.md](patterns.md#6-rdp-remote-desktop) - -3. **Choose origin type** - - Direct IP (single server) → See [configuration.md](configuration.md#direct-ip-origin) - - CNAME (hostname) → See [configuration.md](configuration.md#cname-origin) - - Load balancer (HA/failover) → See [configuration.md](configuration.md#load-balancer-origin) - -## Reading Order - -1. Start with [patterns.md](patterns.md) for your specific protocol -2. Then [configuration.md](configuration.md) for your origin type -3. Check [gotchas.md](gotchas.md) before going to production -4. Use [api.md](api.md) for programmatic access - -## See Also - -- [Cloudflare Docs](https://developers.cloudflare.com/spectrum/) diff --git a/external/develop/devops/cloudflare/references/spectrum/api.md b/external/develop/devops/cloudflare/references/spectrum/api.md deleted file mode 100644 index 645fe2e..0000000 --- a/external/develop/devops/cloudflare/references/spectrum/api.md +++ /dev/null @@ -1,181 +0,0 @@ -## REST API Endpoints - -``` -GET /zones/{zone_id}/spectrum/apps # List apps -POST /zones/{zone_id}/spectrum/apps # Create app -GET /zones/{zone_id}/spectrum/apps/{app_id} # Get app -PUT /zones/{zone_id}/spectrum/apps/{app_id} # Update app -DELETE /zones/{zone_id}/spectrum/apps/{app_id} # Delete app - -GET /zones/{zone_id}/spectrum/analytics/aggregate/current -GET /zones/{zone_id}/spectrum/analytics/events/bytime -GET /zones/{zone_id}/spectrum/analytics/events/summary -``` - -## Request/Response Schemas - -### CreateSpectrumAppRequest - -```typescript -interface CreateSpectrumAppRequest { - protocol: string; // "tcp/22", "udp/53" - dns: { - type: "CNAME" | "ADDRESS"; - name: string; // "ssh.example.com" - }; - origin_direct?: string[]; // ["tcp://192.0.2.1:22"] - origin_dns?: { name: string }; // {"name": "origin.example.com"} - origin_port?: number | { start: number; end: number }; - proxy_protocol?: "off" | "v1" | "v2" | "simple"; - ip_firewall?: boolean; - tls?: "off" | "flexible" | "full" | "strict"; - edge_ips?: { - type: "dynamic" | "static"; - connectivity: "all" | "ipv4" | "ipv6"; - }; - traffic_type?: "direct" | "http" | "https"; - argo_smart_routing?: boolean; -} -``` - -### SpectrumApp Response - -```typescript -interface SpectrumApp { - id: string; - protocol: string; - dns: { type: string; name: string }; - origin_direct?: string[]; - origin_dns?: { name: string }; - origin_port?: number | { start: number; end: number }; - proxy_protocol: string; - ip_firewall: boolean; - tls: string; - edge_ips: { type: string; connectivity: string; ips?: string[] }; - argo_smart_routing: boolean; - created_on: string; - modified_on: string; -} -``` - -## TypeScript SDK - -```typescript -import Cloudflare from 'cloudflare'; - -const client = new Cloudflare({ apiToken: process.env.CLOUDFLARE_API_TOKEN }); - -// Create -const app = await client.spectrum.apps.create({ - zone_id: 'your-zone-id', - protocol: 'tcp/22', - dns: { type: 'CNAME', name: 'ssh.example.com' }, - origin_direct: ['tcp://192.0.2.1:22'], - ip_firewall: true, - tls: 'off', -}); - -// List -const apps = await client.spectrum.apps.list({ zone_id: 'your-zone-id' }); - -// Get -const appDetails = await client.spectrum.apps.get({ zone_id: 'your-zone-id', app_id: app.id }); - -// Update -await client.spectrum.apps.update({ zone_id: 'your-zone-id', app_id: app.id, tls: 'full' }); - -// Delete -await client.spectrum.apps.delete({ zone_id: 'your-zone-id', app_id: app.id }); - -// Analytics -const analytics = await client.spectrum.analytics.aggregate({ - zone_id: 'your-zone-id', - metrics: ['bytesIngress', 'bytesEgress'], - since: new Date(Date.now() - 3600000).toISOString(), -}); -``` - -## Python SDK - -```python -from cloudflare import Cloudflare - -client = Cloudflare(api_token="your-api-token") - -# Create -app = client.spectrum.apps.create( - zone_id="your-zone-id", - protocol="tcp/22", - dns={"type": "CNAME", "name": "ssh.example.com"}, - origin_direct=["tcp://192.0.2.1:22"], - ip_firewall=True, - tls="off", -) - -# List -apps = client.spectrum.apps.list(zone_id="your-zone-id") - -# Get -app_details = client.spectrum.apps.get(zone_id="your-zone-id", app_id=app.id) - -# Update -client.spectrum.apps.update(zone_id="your-zone-id", app_id=app.id, tls="full") - -# Delete -client.spectrum.apps.delete(zone_id="your-zone-id", app_id=app.id) - -# Analytics -analytics = client.spectrum.analytics.aggregate( - zone_id="your-zone-id", - metrics=["bytesIngress", "bytesEgress"], - since=datetime.now() - timedelta(hours=1), -) -``` - -## Go SDK - -```go -import "github.com/cloudflare/cloudflare-go" - -api, _ := cloudflare.NewWithAPIToken("your-api-token") - -// Create -app, _ := api.CreateSpectrumApplication(ctx, "zone-id", cloudflare.SpectrumApplication{ - Protocol: "tcp/22", - DNS: cloudflare.SpectrumApplicationDNS{Type: "CNAME", Name: "ssh.example.com"}, - OriginDirect: []string{"tcp://192.0.2.1:22"}, - IPFirewall: true, - ArgoSmartRouting: true, -}) - -// List -apps, _ := api.SpectrumApplications(ctx, "zone-id") - -// Delete -_ = api.DeleteSpectrumApplication(ctx, "zone-id", app.ID) -``` - -## Analytics API - -**Metrics:** -- `bytesIngress` - Bytes received from clients -- `bytesEgress` - Bytes sent to clients -- `count` - Number of connections -- `duration` - Connection duration (seconds) - -**Dimensions:** -- `event` - Connection event type -- `appID` - Spectrum application ID -- `coloName` - Datacenter name -- `ipVersion` - IPv4 or IPv6 - -**Example:** -```bash -curl "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/spectrum/analytics/aggregate/current?metrics=bytesIngress,bytesEgress,count&dimensions=appID" \ - --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -``` - -## See Also - -- [configuration.md](configuration.md) - Terraform/Pulumi -- [patterns.md](patterns.md) - Protocol examples diff --git a/external/develop/devops/cloudflare/references/spectrum/configuration.md b/external/develop/devops/cloudflare/references/spectrum/configuration.md deleted file mode 100644 index 81aa72f..0000000 --- a/external/develop/devops/cloudflare/references/spectrum/configuration.md +++ /dev/null @@ -1,194 +0,0 @@ -## Origin Types - -### Direct IP Origin - -Use when origin is a single server with static IP. - -**TypeScript SDK:** -```typescript -const app = await client.spectrum.apps.create({ - zone_id: 'your-zone-id', - protocol: 'tcp/22', - dns: { type: 'CNAME', name: 'ssh.example.com' }, - origin_direct: ['tcp://192.0.2.1:22'], - ip_firewall: true, - tls: 'off', -}); -``` - -**Terraform:** -```hcl -resource "cloudflare_spectrum_application" "ssh" { - zone_id = var.zone_id - protocol = "tcp/22" - - dns { - type = "CNAME" - name = "ssh.example.com" - } - - origin_direct = ["tcp://192.0.2.1:22"] - ip_firewall = true - tls = "off" - argo_smart_routing = true -} -``` - -### CNAME Origin - -Use when origin is a hostname (not static IP). Spectrum resolves DNS dynamically. - -**TypeScript SDK:** -```typescript -const app = await client.spectrum.apps.create({ - zone_id: 'your-zone-id', - protocol: 'tcp/3306', - dns: { type: 'CNAME', name: 'db.example.com' }, - origin_dns: { name: 'db-primary.internal.example.com' }, - origin_port: 3306, - tls: 'full', -}); -``` - -**Terraform:** -```hcl -resource "cloudflare_spectrum_application" "database" { - zone_id = var.zone_id - protocol = "tcp/3306" - - dns { - type = "CNAME" - name = "db.example.com" - } - - origin_dns { - name = "db-primary.internal.example.com" - } - - origin_port = 3306 - tls = "full" - argo_smart_routing = true -} -``` - -### Load Balancer Origin - -Use for high availability and failover. - -**Terraform:** -```hcl -resource "cloudflare_load_balancer" "game_lb" { - zone_id = var.zone_id - name = "game-lb.example.com" - default_pool_ids = [cloudflare_load_balancer_pool.game_pool.id] -} - -resource "cloudflare_load_balancer_pool" "game_pool" { - name = "game-primary" - origins { name = "game-1"; address = "192.0.2.1" } - monitor = cloudflare_load_balancer_monitor.tcp_monitor.id -} - -resource "cloudflare_load_balancer_monitor" "tcp_monitor" { - type = "tcp"; port = 25565; interval = 60; timeout = 5 -} - -resource "cloudflare_spectrum_application" "game" { - zone_id = var.zone_id - protocol = "tcp/25565" - dns { type = "CNAME"; name = "game.example.com" } - origin_dns { name = cloudflare_load_balancer.game_lb.name } - origin_port = 25565 -} -``` - -## TLS Configuration - -| Mode | Description | Use Case | Origin Cert | -|------|-------------|----------|-------------| -| `off` | No TLS | Non-encrypted (SSH, gaming) | No | -| `flexible` | TLS client→CF, plain CF→origin | Testing | No | -| `full` | TLS end-to-end, self-signed OK | Production | Yes (any) | -| `strict` | Full + valid cert verification | Max security | Yes (CA) | - -**Example:** -```typescript -const app = await client.spectrum.apps.create({ - zone_id: 'your-zone-id', - protocol: 'tcp/3306', - dns: { type: 'CNAME', name: 'db.example.com' }, - origin_direct: ['tcp://192.0.2.1:3306'], - tls: 'strict', // Validates origin certificate -}); -``` - -## Proxy Protocol - -Forwards real client IP to origin. Origin must support parsing. - -| Version | Protocol | Use Case | -|---------|----------|----------| -| `off` | - | Origin doesn't need client IP | -| `v1` | TCP | Most TCP apps (SSH, databases) | -| `v2` | TCP | High-performance TCP | -| `simple` | UDP | UDP applications | - -**Compatibility:** -- **v1**: HAProxy, nginx, SSH, most databases -- **v2**: HAProxy 1.5+, nginx 1.11+ -- **simple**: Cloudflare-specific UDP format - -**Enable:** -```typescript -const app = await client.spectrum.apps.create({ - // ... - proxy_protocol: 'v1', // Origin must parse PROXY header -}); -``` - -**Origin Config (nginx):** -```nginx -stream { - server { - listen 22 proxy_protocol; - proxy_pass backend:22; - } -} -``` - -## IP Access Rules - -Enable `ip_firewall: true` then configure zone-level firewall rules. - -```typescript -const app = await client.spectrum.apps.create({ - // ... - ip_firewall: true, // Applies zone firewall rules -}); -``` - -## Port Ranges (Enterprise Only) - -```hcl -resource "cloudflare_spectrum_application" "game_cluster" { - zone_id = var.zone_id - protocol = "tcp/25565-25575" - - dns { - type = "CNAME" - name = "games.example.com" - } - - origin_direct = ["tcp://192.0.2.1"] - - origin_port { - start = 25565 - end = 25575 - } -} -``` - -## See Also - -- [patterns.md](patterns.md) - Protocol-specific examples -- [api.md](api.md) - REST/SDK reference diff --git a/external/develop/devops/cloudflare/references/spectrum/gotchas.md b/external/develop/devops/cloudflare/references/spectrum/gotchas.md deleted file mode 100644 index ef31a36..0000000 --- a/external/develop/devops/cloudflare/references/spectrum/gotchas.md +++ /dev/null @@ -1,145 +0,0 @@ -## Common Issues - -### Connection Timeouts - -**Problem:** Connections fail or timeout -**Cause:** Origin firewall blocking Cloudflare IPs, origin service not running, incorrect DNS -**Solution:** -1. Verify origin firewall allows Cloudflare IP ranges -2. Check origin service running on correct port -3. Ensure DNS record is CNAME (not A/AAAA) -4. Verify origin IP/hostname is correct - -```bash -# Test connectivity -nc -zv app.example.com 22 -dig app.example.com -``` - -### Client IP Showing Cloudflare IP - -**Problem:** Origin logs show Cloudflare IPs not real client IPs -**Cause:** Proxy Protocol not enabled or origin not configured -**Solution:** -```typescript -// Enable in Spectrum app -const app = await client.spectrum.apps.create({ - // ... - proxy_protocol: 'v1', // TCP: v1/v2; UDP: simple -}); -``` - -**Origin config:** -- **nginx**: `listen 22 proxy_protocol;` -- **HAProxy**: `bind :22 accept-proxy` - -### TLS Errors - -**Problem:** TLS handshake failures, 525 errors -**Cause:** TLS mode mismatch - -| Error | TLS Mode | Problem | Solution | -|-------|----------|---------|----------| -| Connection refused | `full`/`strict` | Origin not TLS | Use `tls: "off"` or enable TLS | -| 525 cert invalid | `strict` | Self-signed cert | Use `tls: "full"` or valid cert | -| Handshake timeout | `flexible` | Origin expects TLS | Use `tls: "full"` | - -**Debug:** -```bash -openssl s_client -connect app.example.com:443 -showcerts -``` - -### SMTP Reverse DNS - -**Problem:** Email servers reject SMTP via Spectrum -**Cause:** Spectrum IPs lack PTR (reverse DNS) records -**Impact:** Many mail servers require valid rDNS for anti-spam - -**Solution:** -- Outbound SMTP: NOT recommended through Spectrum -- Inbound SMTP: Use Cloudflare Email Routing -- Internal relay: Whitelist Spectrum IPs on destination - -### Proxy Protocol Compatibility - -**Problem:** Connection works but app behaves incorrectly -**Cause:** Origin doesn't support Proxy Protocol - -**Solution:** -1. Verify origin supports version (v1: widely supported, v2: HAProxy 1.5+/nginx 1.11+) -2. Test with `proxy_protocol: 'off'` first -3. Configure origin to parse headers - -**nginx TCP:** -```nginx -stream { - server { - listen 22 proxy_protocol; - proxy_pass backend:22; - } -} -``` - -**HAProxy:** -``` -frontend ft_ssh - bind :22 accept-proxy -``` - -### Analytics Data Retention - -**Problem:** Historical data not available -**Cause:** Retention varies by plan - -| Plan | Real-time | Historical | -|------|-----------|------------| -| Pro | Last hour | ❌ | -| Business | Last hour | Limited | -| Enterprise | Last hour | 90+ days | - -**Solution:** Query within retention window or export to external system - -### Enterprise-Only Features - -**Problem:** Feature unavailable/errors -**Cause:** Requires Enterprise plan - -**Enterprise-only:** -- Port ranges (`tcp/25565-25575`) -- All TCP/UDP ports (Pro/Business: selected only) -- Extended analytics retention -- Advanced load balancing - -### IPv6 Considerations - -**Problem:** IPv6 clients can't connect or origin doesn't support IPv6 -**Solution:** Configure `edge_ips.connectivity` - -```typescript -const app = await client.spectrum.apps.create({ - // ... - edge_ips: { - type: 'dynamic', - connectivity: 'ipv4', // Options: 'all', 'ipv4', 'ipv6' - }, -}); -``` - -**Options:** -- `all`: Dual-stack (default, requires origin support both) -- `ipv4`: IPv4 only (use if origin lacks IPv6) -- `ipv6`: IPv6 only (rare) - -## Limits - -| Resource | Pro/Business | Enterprise | -|----------|--------------|------------| -| Max apps | ~10-15 | 100+ | -| Protocols | Selected | All TCP/UDP | -| Port ranges | ❌ | ✅ | -| Analytics | ~1 hour | 90+ days | - -## See Also - -- [patterns.md](patterns.md) - Protocol examples -- [configuration.md](configuration.md) - TLS/Proxy setup diff --git a/external/develop/devops/cloudflare/references/spectrum/patterns.md b/external/develop/devops/cloudflare/references/spectrum/patterns.md deleted file mode 100644 index 4032486..0000000 --- a/external/develop/devops/cloudflare/references/spectrum/patterns.md +++ /dev/null @@ -1,196 +0,0 @@ -## Common Use Cases - -### 1. SSH Server Protection - -**Terraform:** -```hcl -resource "cloudflare_spectrum_application" "ssh" { - zone_id = var.zone_id - protocol = "tcp/22" - - dns { - type = "CNAME" - name = "ssh.example.com" - } - - origin_direct = ["tcp://10.0.1.5:22"] - ip_firewall = true - argo_smart_routing = true -} -``` - -**Benefits:** Hide origin IP, DDoS protection, IP firewall, Argo reduces latency - -### 2. Game Server - -**TypeScript (Minecraft):** -```typescript -const app = await client.spectrum.apps.create({ - zone_id: 'your-zone-id', - protocol: 'tcp/25565', - dns: { type: 'CNAME', name: 'mc.example.com' }, - origin_direct: ['tcp://192.168.1.10:25565'], - proxy_protocol: 'v1', // Preserves player IPs - argo_smart_routing: true, -}); -``` - -**Benefits:** DDoS protection, hide origin IP, Proxy Protocol for player IPs/bans, Argo reduces latency - -### 3. MQTT Broker - -IoT device communication. - -**TypeScript:** -```typescript -const mqttApp = await client.spectrum.apps.create({ - zone_id: 'your-zone-id', - protocol: 'tcp/8883', // Use 1883 for plain MQTT - dns: { type: 'CNAME', name: 'mqtt.example.com' }, - origin_direct: ['tcp://mqtt-broker.internal:8883'], - tls: 'full', // Use 'off' for plain MQTT -}); -``` - -**Benefits:** DDoS protection, hide broker IP, TLS termination at edge - -### 4. SMTP Relay - -Email submission (port 587). **WARNING**: See [gotchas.md](gotchas.md#smtp-reverse-dns) - -**Terraform:** -```hcl -resource "cloudflare_spectrum_application" "smtp" { - zone_id = var.zone_id - protocol = "tcp/587" - - dns { - type = "CNAME" - name = "smtp.example.com" - } - - origin_direct = ["tcp://mail-server.internal:587"] - tls = "full" # STARTTLS support -} -``` - -**Limitations:** -- Spectrum IPs lack reverse DNS (PTR records) -- Many mail servers reject without valid rDNS -- Best for internal/trusted relay only - -### 5. Database Proxy - -MySQL/PostgreSQL. **Use with caution** - security critical. - -**PostgreSQL:** -```typescript -const postgresApp = await client.spectrum.apps.create({ - zone_id: 'your-zone-id', - protocol: 'tcp/5432', - dns: { type: 'CNAME', name: 'postgres.example.com' }, - origin_dns: { name: 'db-primary.internal.example.com' }, - origin_port: 5432, - tls: 'strict', // REQUIRED - ip_firewall: true, // REQUIRED -}); -``` - -**MySQL:** -```hcl -resource "cloudflare_spectrum_application" "mysql" { - zone_id = var.zone_id - protocol = "tcp/3306" - - dns { - type = "CNAME" - name = "mysql.example.com" - } - - origin_dns { - name = "mysql-primary.internal.example.com" - } - - origin_port = 3306 - tls = "strict" - ip_firewall = true -} -``` - -**Security:** -- ALWAYS use `tls: "strict"` -- ALWAYS use `ip_firewall: true` -- Restrict to known IPs via zone firewall -- Use strong DB authentication -- Consider VPN or Cloudflare Access instead - -### 6. RDP (Remote Desktop) - -**Requires IP firewall.** - -**Terraform:** -```hcl -resource "cloudflare_spectrum_application" "rdp" { - zone_id = var.zone_id - protocol = "tcp/3389" - - dns { - type = "CNAME" - name = "rdp.example.com" - } - - origin_direct = ["tcp://windows-server.internal:3389"] - tls = "off" # RDP has own encryption - ip_firewall = true # REQUIRED -} -``` - -**Security:** ALWAYS `ip_firewall: true`, whitelist admin IPs, RDP is DDoS/brute-force target - -### 7. Multi-Origin Failover - -High availability with load balancer. - -**Terraform:** -```hcl -resource "cloudflare_load_balancer" "database_lb" { - zone_id = var.zone_id - name = "db-lb.example.com" - default_pool_ids = [cloudflare_load_balancer_pool.db_primary.id] - fallback_pool_id = cloudflare_load_balancer_pool.db_secondary.id -} - -resource "cloudflare_load_balancer_pool" "db_primary" { - name = "db-primary-pool" - origins { name = "db-1"; address = "192.0.2.1" } - monitor = cloudflare_load_balancer_monitor.postgres_monitor.id -} - -resource "cloudflare_load_balancer_pool" "db_secondary" { - name = "db-secondary-pool" - origins { name = "db-2"; address = "192.0.2.2" } - monitor = cloudflare_load_balancer_monitor.postgres_monitor.id -} - -resource "cloudflare_load_balancer_monitor" "postgres_monitor" { - type = "tcp"; port = 5432; interval = 30; timeout = 5 -} - -resource "cloudflare_spectrum_application" "postgres_ha" { - zone_id = var.zone_id - protocol = "tcp/5432" - dns { type = "CNAME"; name = "postgres.example.com" } - origin_dns { name = cloudflare_load_balancer.database_lb.name } - origin_port = 5432 - tls = "strict" - ip_firewall = true -} -``` - -**Benefits:** Automatic failover, health monitoring, traffic distribution, zero-downtime deployments - -## See Also - -- [configuration.md](configuration.md) - Origin type setup -- [gotchas.md](gotchas.md) - Protocol limitations -- [api.md](api.md) - SDK reference diff --git a/external/develop/devops/cloudflare/references/static-assets/README.md b/external/develop/devops/cloudflare/references/static-assets/README.md index 58f3f00..caa2237 100644 --- a/external/develop/devops/cloudflare/references/static-assets/README.md +++ b/external/develop/devops/cloudflare/references/static-assets/README.md @@ -4,12 +4,12 @@ Use Workers Static Assets for new static sites, SPAs, generated sites, and appli | Task | Documentation | |------|---------------| -| Set up and deploy a static site or application | [Get started](https://developers.cloudflare.com/workers/static-assets/get-started/) | -| Choose configuration and an optional asset binding | [Configuration and bindings](https://developers.cloudflare.com/workers/static-assets/binding/) | -| Serve a client-rendered application | [SPA routing](https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/) | -| Serve generated HTML and custom error pages | [SSG routing](https://developers.cloudflare.com/workers/static-assets/routing/static-site-generation/) | -| Use a full-stack framework | [Full-stack application guides](https://developers.cloudflare.com/workers/static-assets/routing/full-stack-application/) | -| Evaluate moving an existing Pages project | [Pages migration guide](https://developers.cloudflare.com/workers/static-assets/migration-guides/migrate-from-pages/) | +| Set up and deploy a static site or application | [Get started](https://developers.cloudflare.com/workers/static-assets/get-started/index.md) | +| Choose configuration and an optional asset binding | [Configuration and bindings](https://developers.cloudflare.com/workers/static-assets/binding/index.md) | +| Serve a client-rendered application | [SPA routing](https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/index.md) | +| Serve generated HTML and custom error pages | [SSG routing](https://developers.cloudflare.com/workers/static-assets/routing/static-site-generation/index.md) | +| Use a full-stack framework | [Full-stack application guides](https://developers.cloudflare.com/workers/static-assets/routing/full-stack-application/index.md) | +| Evaluate moving an existing Pages project | [Pages migration guide](https://developers.cloudflare.com/workers/static-assets/migration-guides/migrate-from-pages/index.md) | Do not choose a platform solely from the framework name. For an existing Pages project, inspect its current features and migration requirements before proposing a move. diff --git a/external/develop/devops/cloudflare/references/static-assets/api.md b/external/develop/devops/cloudflare/references/static-assets/api.md index 45ac2dc..f542630 100644 --- a/external/develop/devops/cloudflare/references/static-assets/api.md +++ b/external/develop/devops/cloudflare/references/static-assets/api.md @@ -4,12 +4,12 @@ Read the binding reference before implementing calls. Check the configured bindi | Task | Documentation | |------|---------------| -| Forward a request or fetch a specific asset | [Runtime API reference](https://developers.cloudflare.com/workers/static-assets/binding/#runtime-api-reference) | -| Understand how binding requests apply HTML and fallback settings | [Binding fetch behavior](https://developers.cloudflare.com/workers/static-assets/binding/#runtime-api-reference) and [HTML handling](https://developers.cloudflare.com/workers/static-assets/routing/advanced/html-handling/) | -| Run authorization or transform content before serving | [Run your Worker script first](https://developers.cloudflare.com/workers/static-assets/routing/worker-script/#run-your-worker-script-first) | -| Inspect default MIME, cache, and validation headers | [Default headers](https://developers.cloudflare.com/workers/static-assets/headers/#default-headers) | -| Add or override response headers | [Custom headers and Worker-response caveat](https://developers.cloudflare.com/workers/static-assets/headers/#custom-headers) | -| Fetch assets imported by Vite | [Vite asset features](https://developers.cloudflare.com/workers/vite-plugin/reference/static-assets/#features) | +| Forward a request or fetch a specific asset | [Runtime API reference](https://developers.cloudflare.com/workers/static-assets/binding/index.md#runtime-api-reference) | +| Understand how binding requests apply HTML and fallback settings | [Binding fetch behavior](https://developers.cloudflare.com/workers/static-assets/binding/index.md#runtime-api-reference) and [HTML handling](https://developers.cloudflare.com/workers/static-assets/routing/advanced/html-handling/index.md) | +| Run authorization or transform content before serving | [Run your Worker script first](https://developers.cloudflare.com/workers/static-assets/routing/worker-script/index.md#run-your-worker-script-first) | +| Inspect default MIME, cache, and validation headers | [Default headers](https://developers.cloudflare.com/workers/static-assets/headers/index.md#default-headers) | +| Add or override response headers | [Custom headers and Worker-response caveat](https://developers.cloudflare.com/workers/static-assets/headers/index.md#custom-headers) | +| Fetch assets imported by Vite | [Vite asset features](https://developers.cloudflare.com/workers/vite-plugin/reference/static-assets/index.md#features) | When selecting a different asset, construct a full URL using the incoming request as the base. Preserve the returned status and headers when transforming a response; do not collapse every unsuccessful response into an application 404. diff --git a/external/develop/devops/cloudflare/references/static-assets/configuration.md b/external/develop/devops/cloudflare/references/static-assets/configuration.md index f093329..e540448 100644 --- a/external/develop/devops/cloudflare/references/static-assets/configuration.md +++ b/external/develop/devops/cloudflare/references/static-assets/configuration.md @@ -4,13 +4,13 @@ Inspect the build script, output directory, Wrangler configuration, and any fram | Task | Documentation | |------|---------------| -| Set the asset directory and exclude non-public files | [Directory](https://developers.cloudflare.com/workers/static-assets/binding/#directory) and [ignoring assets](https://developers.cloudflare.com/workers/static-assets/binding/#ignoring-assets) | -| Make assets available to Worker code | [Asset binding configuration](https://developers.cloudflare.com/workers/static-assets/binding/#binding) | -| Select paths that must run Worker logic before asset serving | [Worker-first configuration](https://developers.cloudflare.com/workers/static-assets/binding/#run_worker_first) | -| Configure SPA fallback and navigation behavior | [SPA configuration](https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/#configuration) | -| Configure generated HTML and missing-page responses | [SSG configuration](https://developers.cloudflare.com/workers/static-assets/routing/static-site-generation/#configuration) | -| Choose canonical HTML URLs and trailing slash handling | [HTML handling modes](https://developers.cloudflare.com/workers/static-assets/routing/advanced/html-handling/) | -| Configure assets produced by the Cloudflare Vite plugin | [Vite asset configuration](https://developers.cloudflare.com/workers/vite-plugin/reference/static-assets/#configuration) | -| Configure static response headers or redirects | [Headers](https://developers.cloudflare.com/workers/static-assets/headers/) and [redirects](https://developers.cloudflare.com/workers/static-assets/redirects/) | +| Set the asset directory and exclude non-public files | [Directory](https://developers.cloudflare.com/workers/static-assets/binding/index.md#directory) and [ignoring assets](https://developers.cloudflare.com/workers/static-assets/binding/index.md#ignoring-assets) | +| Make assets available to Worker code | [Asset binding configuration](https://developers.cloudflare.com/workers/static-assets/binding/index.md#binding) | +| Select paths that must run Worker logic before asset serving | [Worker-first configuration](https://developers.cloudflare.com/workers/static-assets/binding/index.md#run_worker_first) | +| Configure SPA fallback and navigation behavior | [SPA configuration](https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/index.md#configuration) | +| Configure generated HTML and missing-page responses | [SSG configuration](https://developers.cloudflare.com/workers/static-assets/routing/static-site-generation/index.md#configuration) | +| Choose canonical HTML URLs and trailing slash handling | [HTML handling modes](https://developers.cloudflare.com/workers/static-assets/routing/advanced/html-handling/index.md) | +| Configure assets produced by the Cloudflare Vite plugin | [Vite asset configuration](https://developers.cloudflare.com/workers/vite-plugin/reference/static-assets/index.md#configuration) | +| Configure static response headers or redirects | [Headers](https://developers.cloudflare.com/workers/static-assets/headers/index.md) and [redirects](https://developers.cloudflare.com/workers/static-assets/redirects/index.md) | For Vite projects, inspect generated output before overriding asset paths. For protected routes, ensure that the selected routing configuration reaches the authorization logic before returning an asset. Use [patterns.md](patterns.md) to decide routing intent and [gotchas.md](gotchas.md) to verify it. diff --git a/external/develop/devops/cloudflare/references/static-assets/gotchas.md b/external/develop/devops/cloudflare/references/static-assets/gotchas.md index af98692..841f68d 100644 --- a/external/develop/devops/cloudflare/references/static-assets/gotchas.md +++ b/external/develop/devops/cloudflare/references/static-assets/gotchas.md @@ -4,15 +4,15 @@ Compare the deployed build output and effective configuration with the exact req | Symptom or decision | Documentation | |---------------------|---------------| -| Asset missing or unexpected files uploaded | [Asset directory](https://developers.cloudflare.com/workers/static-assets/binding/#directory) and [ignore rules](https://developers.cloudflare.com/workers/static-assets/binding/#ignoring-assets) | -| Worker is bypassed for an asset or protected path | [Worker-first routing](https://developers.cloudflare.com/workers/static-assets/routing/worker-script/#run-your-worker-script-first) | -| Browser navigation returns HTML for an API path | [SPA navigation requests](https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/#navigation-requests) and [advanced routing](https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/#advanced-routing-control) | -| Unexpected redirects or trailing slashes | [HTML handling](https://developers.cloudflare.com/workers/static-assets/routing/advanced/html-handling/) | -| A missing route serves the wrong fallback | [SPA routing](https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/) or [SSG custom 404 pages](https://developers.cloudflare.com/workers/static-assets/routing/static-site-generation/#custom-404-pages) | -| Cache behavior or custom headers differ from expectations | [Default and custom headers](https://developers.cloudflare.com/workers/static-assets/headers/) | -| Vite development and deployment behave differently | [Vite asset configuration and output](https://developers.cloudflare.com/workers/vite-plugin/reference/static-assets/) | -| Worker-first requests return 429 or affect cost | [Billing and limitations](https://developers.cloudflare.com/workers/static-assets/billing-and-limitations/) | -| Asset count or file size exceeds deployment limits | [Static asset platform limits](https://developers.cloudflare.com/workers/platform/limits/#static-assets) | -| Placement adds latency to asset requests | [Worker routing and placement caveat](https://developers.cloudflare.com/workers/static-assets/routing/worker-script/#run-your-worker-script-first) | +| Asset missing or unexpected files uploaded | [Asset directory](https://developers.cloudflare.com/workers/static-assets/binding/index.md#directory) and [ignore rules](https://developers.cloudflare.com/workers/static-assets/binding/index.md#ignoring-assets) | +| Worker is bypassed for an asset or protected path | [Worker-first routing](https://developers.cloudflare.com/workers/static-assets/routing/worker-script/index.md#run-your-worker-script-first) | +| Browser navigation returns HTML for an API path | [SPA navigation requests](https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/index.md#navigation-requests) and [advanced routing](https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/index.md#advanced-routing-control) | +| Unexpected redirects or trailing slashes | [HTML handling](https://developers.cloudflare.com/workers/static-assets/routing/advanced/html-handling/index.md) | +| A missing route serves the wrong fallback | [SPA routing](https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/index.md) or [SSG custom 404 pages](https://developers.cloudflare.com/workers/static-assets/routing/static-site-generation/index.md#custom-404-pages) | +| Cache behavior or custom headers differ from expectations | [Default and custom headers](https://developers.cloudflare.com/workers/static-assets/headers/index.md) | +| Vite development and deployment behave differently | [Vite asset configuration and output](https://developers.cloudflare.com/workers/vite-plugin/reference/static-assets/index.md) | +| Worker-first requests return 429 or affect cost | [Billing and limitations](https://developers.cloudflare.com/workers/static-assets/billing-and-limitations/index.md) | +| Asset count or file size exceeds deployment limits | [Static asset platform limits](https://developers.cloudflare.com/workers/platform/limits/index.md#static-assets) | +| Placement adds latency to asset requests | [Worker routing and placement caveat](https://developers.cloudflare.com/workers/static-assets/routing/worker-script/index.md#run-your-worker-script-first) | Verify a public asset, API endpoint, protected path, and missing URL against the routing intent. Inspect response status, redirect location, and cache headers before changing fallback or cache configuration. Return to [configuration.md](configuration.md) and [api.md](api.md) for the relevant settings and binding behavior. diff --git a/external/develop/devops/cloudflare/references/static-assets/patterns.md b/external/develop/devops/cloudflare/references/static-assets/patterns.md index 09ed6d3..b83ffd6 100644 --- a/external/develop/devops/cloudflare/references/static-assets/patterns.md +++ b/external/develop/devops/cloudflare/references/static-assets/patterns.md @@ -4,13 +4,13 @@ Decide which requests need application logic before choosing configuration. Reco | Routing need | Documentation | |--------------|---------------| -| Serve public assets with minimal Worker involvement | [Default Worker routing](https://developers.cloudflare.com/workers/static-assets/routing/worker-script/) | -| Combine a SPA with API routes | [Advanced SPA routing control](https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/#advanced-routing-control) | -| Require authentication or transform assets before serving | [Run Worker before each request](https://developers.cloudflare.com/workers/static-assets/routing/worker-script/#run-worker-before-each-request) | -| Route an OAuth callback or selected dynamic paths to the Worker | [Run Worker first for selective paths](https://developers.cloudflare.com/workers/static-assets/routing/worker-script/#run-worker-first-for-selective-paths) | -| Select assets for locale or experiment variants | [Asset binding API](https://developers.cloudflare.com/workers/static-assets/binding/#runtime-api-reference) and [Worker-first routing](https://developers.cloudflare.com/workers/static-assets/binding/#run_worker_first) | -| Serve generated HTML with a custom 404 | [Custom 404 pages](https://developers.cloudflare.com/workers/static-assets/routing/static-site-generation/#custom-404-pages) | -| Apply static cache/security headers or redirects | [Custom headers](https://developers.cloudflare.com/workers/static-assets/headers/#custom-headers) and [redirects](https://developers.cloudflare.com/workers/static-assets/redirects/) | +| Serve public assets with minimal Worker involvement | [Default Worker routing](https://developers.cloudflare.com/workers/static-assets/routing/worker-script/index.md) | +| Combine a SPA with API routes | [Advanced SPA routing control](https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/index.md#advanced-routing-control) | +| Require authentication or transform assets before serving | [Run Worker before each request](https://developers.cloudflare.com/workers/static-assets/routing/worker-script/index.md#run-worker-before-each-request) | +| Route an OAuth callback or selected dynamic paths to the Worker | [Run Worker first for selective paths](https://developers.cloudflare.com/workers/static-assets/routing/worker-script/index.md#run-worker-first-for-selective-paths) | +| Select assets for locale or experiment variants | [Asset binding API](https://developers.cloudflare.com/workers/static-assets/binding/index.md#runtime-api-reference) and [Worker-first routing](https://developers.cloudflare.com/workers/static-assets/binding/index.md#run_worker_first) | +| Serve generated HTML with a custom 404 | [Custom 404 pages](https://developers.cloudflare.com/workers/static-assets/routing/static-site-generation/index.md#custom-404-pages) | +| Apply static cache/security headers or redirects | [Custom headers](https://developers.cloudflare.com/workers/static-assets/headers/index.md#custom-headers) and [redirects](https://developers.cloudflare.com/workers/static-assets/redirects/index.md) | Keep public asset paths eligible for direct serving when they do not need application logic. Ensure protected paths cannot bypass the authorization handler through an asset match or navigation fallback. For locale or experiment routing, map accepted variants to known build outputs and define a fallback explicitly. diff --git a/external/develop/devops/cloudflare/references/stream/README.md b/external/develop/devops/cloudflare/references/stream/README.md index e78419f..e3e9d4b 100644 --- a/external/develop/devops/cloudflare/references/stream/README.md +++ b/external/develop/devops/cloudflare/references/stream/README.md @@ -6,12 +6,12 @@ Use Stream for on-demand video upload and playback or live broadcasting. Start w | Task | Read | |---|---| -| Let users upload without exposing an API token | [Direct creator uploads](https://developers.cloudflare.com/stream/uploading-videos/direct-creator-uploads/) | -| Choose server upload, resumable upload, or import from a URL | [Upload methods and supported formats](https://developers.cloudflare.com/stream/uploading-videos/) | -| Embed playback or integrate an existing player | [Stream Player](https://developers.cloudflare.com/stream/viewing-videos/using-the-stream-player/) or [HLS/DASH players](https://developers.cloudflare.com/stream/viewing-videos/using-own-player/) | -| Restrict viewing to authorized users or embedding origins | [Secure your Stream](https://developers.cloudflare.com/stream/viewing-videos/securing-your-stream/) | +| Let users upload without exposing an API token | [Direct creator uploads](https://developers.cloudflare.com/stream/uploading-videos/direct-creator-uploads/index.md) | +| Choose server upload, resumable upload, or import from a URL | [Upload methods and supported formats](https://developers.cloudflare.com/stream/uploading-videos/index.md) | +| Embed playback or integrate an existing player | [Stream Player](https://developers.cloudflare.com/stream/viewing-videos/using-the-stream-player/index.md) or [HLS/DASH players](https://developers.cloudflare.com/stream/viewing-videos/using-own-player/index.md) | +| Restrict viewing to authorized users or embedding origins | [Secure your Stream](https://developers.cloudflare.com/stream/viewing-videos/securing-your-stream/index.md) | | Broadcast live, replay recordings, or simulcast | [Live workflow routing](./api-live.md) | -| Check usage, costs, or upload constraints | [Analytics](https://developers.cloudflare.com/stream/getting-analytics/), [pricing](https://developers.cloudflare.com/stream/pricing/), and [upload requirements](https://developers.cloudflare.com/stream/uploading-videos/) | +| Check usage, costs, or upload constraints | [Analytics](https://developers.cloudflare.com/stream/getting-analytics/index.md), [pricing](https://developers.cloudflare.com/stream/pricing/index.md), and [upload requirements](https://developers.cloudflare.com/stream/uploading-videos/index.md) | ## In This Reference diff --git a/external/develop/devops/cloudflare/references/stream/api-live.md b/external/develop/devops/cloudflare/references/stream/api-live.md index 2db8910..7af2e96 100644 --- a/external/develop/devops/cloudflare/references/stream/api-live.md +++ b/external/develop/devops/cloudflare/references/stream/api-live.md @@ -4,15 +4,15 @@ Choose the ingest protocol and viewer experience before creating live inputs. Re | Task | Read | |---|---| -| Create an RTMPS/SRT input and connect an encoder | [Start a live stream](https://developers.cloudflare.com/stream/stream-live/start-stream-live/) | -| Update inputs, recording, retention, or stream keys | [Manage live inputs](https://developers.cloudflare.com/stream/stream-live/start-stream-live/#manage-live-inputs) | -| Choose persistent-channel playback versus a particular video | [View by live input ID or video ID](https://developers.cloudflare.com/stream/stream-live/watch-live-stream/#view-by-live-input-id-or-video-id) | -| Find and replay recorded broadcasts | [Replay recordings](https://developers.cloudflare.com/stream/stream-live/replay-recordings/) | -| Forward broadcasts to external platforms | [Simulcasting configuration and limits](https://developers.cloudflare.com/stream/stream-live/simulcasting/) | -| Receive connection and disconnection notifications | [Live webhooks](https://developers.cloudflare.com/stream/stream-live/webhooks/) | -| Publish and play using WHIP/WHEP | [WebRTC requirements and endpoints](https://developers.cloudflare.com/stream/webrtc-beta/) and [browser integration](https://developers.cloudflare.com/stream/examples/browser-based-webrtc/) | -| Diagnose encoder, buffering, or latency problems | [Live troubleshooting](https://developers.cloudflare.com/stream/stream-live/troubleshooting/) | +| Create an RTMPS/SRT input and connect an encoder | [Start a live stream](https://developers.cloudflare.com/stream/stream-live/start-stream-live/index.md) | +| Update inputs, recording, retention, or stream keys | [Manage live inputs](https://developers.cloudflare.com/stream/stream-live/start-stream-live/index.md#manage-live-inputs) | +| Choose persistent-channel playback versus a particular video | [View by live input ID or video ID](https://developers.cloudflare.com/stream/stream-live/watch-live-stream/index.md#view-by-live-input-id-or-video-id) | +| Find and replay recorded broadcasts | [Replay recordings](https://developers.cloudflare.com/stream/stream-live/replay-recordings/index.md) | +| Forward broadcasts to external platforms | [Simulcasting configuration and limits](https://developers.cloudflare.com/stream/stream-live/simulcasting/index.md) | +| Receive connection and disconnection notifications | [Live webhooks](https://developers.cloudflare.com/stream/stream-live/webhooks/index.md) | +| Publish and play using WHIP/WHEP | [WebRTC requirements and endpoints](https://developers.cloudflare.com/stream/webrtc-beta/index.md) and [browser integration](https://developers.cloudflare.com/stream/examples/browser-based-webrtc/index.md) | +| Diagnose encoder, buffering, or latency problems | [Live troubleshooting](https://developers.cloudflare.com/stream/stream-live/troubleshooting/index.md) | -Keep publishing credentials separate from viewer playback data. Decide whether the application stores a reusable live input, individual recording IDs, or both. Route recording processing events through [video webhooks](https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/); live connection events have their own notification setup. +Keep publishing credentials separate from viewer playback data. Decide whether the application stores a reusable live input, individual recording IDs, or both. Route recording processing events through [video webhooks](https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/index.md); live connection events have their own notification setup. See [configuration.md](./configuration.md) for access decisions and [patterns.md](./patterns.md) for application state handling. diff --git a/external/develop/devops/cloudflare/references/stream/api.md b/external/develop/devops/cloudflare/references/stream/api.md index 25b9984..9a87fd4 100644 --- a/external/develop/devops/cloudflare/references/stream/api.md +++ b/external/develop/devops/cloudflare/references/stream/api.md @@ -4,18 +4,18 @@ Read the task-specific documentation for request schemas, SDK examples, and resp | Task | Read | |---|---| -| Issue a one-time upload URL to an end user | [Direct creator uploads](https://developers.cloudflare.com/stream/uploading-videos/direct-creator-uploads/) | -| Resume uploads or handle unreliable connections | [Resumable uploads and requirements](https://developers.cloudflare.com/stream/uploading-videos/resumable-uploads/) | -| Import a video already hosted elsewhere | [Upload via link](https://developers.cloudflare.com/stream/uploading-videos/upload-via-link/) | -| Embed an iframe or React player | [Use the Stream Player](https://developers.cloudflare.com/stream/viewing-videos/using-the-stream-player/) | -| Use HLS/DASH with an existing player | [Use your own player](https://developers.cloudflare.com/stream/viewing-videos/using-own-player/) | -| Issue playback tokens | [Signed URLs and token-generation choices](https://developers.cloudflare.com/stream/viewing-videos/securing-your-stream/#three-ways-to-generate-signed-tokens) | -| Generate previews or downloadable files | [Thumbnails](https://developers.cloudflare.com/stream/viewing-videos/displaying-thumbnails/) and [downloads](https://developers.cloudflare.com/stream/viewing-videos/download-videos/) | -| Upload captions or generate them with Stream | [Add captions](https://developers.cloudflare.com/stream/edit-videos/adding-captions/) | -| Apply branding during upload | [Watermarks](https://developers.cloudflare.com/stream/edit-videos/applying-watermarks/) | -| Trim an on-demand video | [Video clipping](https://developers.cloudflare.com/stream/edit-videos/video-clipping/) | -| List and filter videos through the REST API | [List videos](https://developers.cloudflare.com/api/resources/stream/methods/list/) | -| Manage videos from a Worker | [Stream binding methods](https://developers.cloudflare.com/stream/manage-video-library/bindings/#methods) | -| React to encoding success or failure | [Video webhooks](https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/) | +| Issue a one-time upload URL to an end user | [Direct creator uploads](https://developers.cloudflare.com/stream/uploading-videos/direct-creator-uploads/index.md) | +| Resume uploads or handle unreliable connections | [Resumable uploads and requirements](https://developers.cloudflare.com/stream/uploading-videos/resumable-uploads/index.md) | +| Import a video already hosted elsewhere | [Upload via link](https://developers.cloudflare.com/stream/uploading-videos/upload-via-link/index.md) | +| Embed an iframe or React player | [Use the Stream Player](https://developers.cloudflare.com/stream/viewing-videos/using-the-stream-player/index.md) | +| Use HLS/DASH with an existing player | [Use your own player](https://developers.cloudflare.com/stream/viewing-videos/using-own-player/index.md) | +| Issue playback tokens | [Signed URLs and token-generation choices](https://developers.cloudflare.com/stream/viewing-videos/securing-your-stream/index.md#three-ways-to-generate-signed-tokens) | +| Generate previews or downloadable files | [Thumbnails](https://developers.cloudflare.com/stream/viewing-videos/displaying-thumbnails/index.md) and [downloads](https://developers.cloudflare.com/stream/viewing-videos/download-videos/index.md) | +| Upload captions or generate them with Stream | [Add captions](https://developers.cloudflare.com/stream/edit-videos/adding-captions/index.md) | +| Apply branding during upload | [Watermarks](https://developers.cloudflare.com/stream/edit-videos/applying-watermarks/index.md) | +| Trim an on-demand video | [Video clipping](https://developers.cloudflare.com/stream/edit-videos/video-clipping/index.md) | +| List and filter videos through the REST API | [List videos](https://developers.cloudflare.com/api/resources/stream/methods/list/index.md) | +| Manage videos from a Worker | [Stream binding methods](https://developers.cloudflare.com/stream/manage-video-library/bindings/index.md#methods) | +| React to encoding success or failure | [Video webhooks](https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/index.md) | Keep the returned video UID associated with the application's owning user or record. Treat upload completion and playback readiness as separate application states; see [workflow decisions](./patterns.md). diff --git a/external/develop/devops/cloudflare/references/stream/configuration.md b/external/develop/devops/cloudflare/references/stream/configuration.md index 4f3aaa6..363d5ac 100644 --- a/external/develop/devops/cloudflare/references/stream/configuration.md +++ b/external/develop/devops/cloudflare/references/stream/configuration.md @@ -4,14 +4,14 @@ Inspect the existing runtime, API client or binding, authentication layer, playe | Task | Read | |---|---| -| Configure Stream in a Worker and use its binding | [Stream binding setup](https://developers.cloudflare.com/stream/manage-video-library/bindings/#setup) | -| Set creator upload constraints and metadata | [Direct creator uploads](https://developers.cloudflare.com/stream/uploading-videos/direct-creator-uploads/) | -| Choose a token endpoint, Worker binding, or signing key | [Token-generation choices](https://developers.cloudflare.com/stream/viewing-videos/securing-your-stream/#three-ways-to-generate-signed-tokens) | -| Require private playback or apply token restrictions | [Secure your Stream](https://developers.cloudflare.com/stream/viewing-videos/securing-your-stream/) | -| Restrict embedding origins | [Allowed origins](https://developers.cloudflare.com/stream/viewing-videos/securing-your-stream/#allowed-origins) | -| Configure processing notifications and their secret | [Video webhooks](https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/) | -| Configure live recording or external destinations | [Live inputs](https://developers.cloudflare.com/stream/stream-live/start-stream-live/) and [simulcasting](https://developers.cloudflare.com/stream/stream-live/simulcasting/) | -| Choose player configuration or framework integration | [Stream Player](https://developers.cloudflare.com/stream/viewing-videos/using-the-stream-player/) | +| Configure Stream in a Worker and use its binding | [Stream binding setup](https://developers.cloudflare.com/stream/manage-video-library/bindings/index.md#setup) | +| Set creator upload constraints and metadata | [Direct creator uploads](https://developers.cloudflare.com/stream/uploading-videos/direct-creator-uploads/index.md) | +| Choose a token endpoint, Worker binding, or signing key | [Token-generation choices](https://developers.cloudflare.com/stream/viewing-videos/securing-your-stream/index.md#three-ways-to-generate-signed-tokens) | +| Require private playback or apply token restrictions | [Secure your Stream](https://developers.cloudflare.com/stream/viewing-videos/securing-your-stream/index.md) | +| Restrict embedding origins | [Allowed origins](https://developers.cloudflare.com/stream/viewing-videos/securing-your-stream/index.md#allowed-origins) | +| Configure processing notifications and their secret | [Video webhooks](https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/index.md) | +| Configure live recording or external destinations | [Live inputs](https://developers.cloudflare.com/stream/stream-live/start-stream-live/index.md) and [simulcasting](https://developers.cloudflare.com/stream/stream-live/simulcasting/index.md) | +| Choose player configuration or framework integration | [Stream Player](https://developers.cloudflare.com/stream/viewing-videos/using-the-stream-player/index.md) | Use the project's server-side secret handling for API tokens, signing keys, and webhook secrets. Identify the authorization check that permits issuing an upload URL or playback token; Stream configuration alone does not define the application's user entitlements. diff --git a/external/develop/devops/cloudflare/references/stream/gotchas.md b/external/develop/devops/cloudflare/references/stream/gotchas.md index c6ae20d..0c02ec8 100644 --- a/external/develop/devops/cloudflare/references/stream/gotchas.md +++ b/external/develop/devops/cloudflare/references/stream/gotchas.md @@ -4,15 +4,15 @@ Identify whether the failure occurs during upload, encoding, authorization, play | Symptom or check | Read | |---|---| -| Unsupported file or upload constraint failure | [Supported formats and upload requirements](https://developers.cloudflare.com/stream/uploading-videos/) | -| Large upload fails or restarts after interruption | [Resumable upload requirements](https://developers.cloudflare.com/stream/uploading-videos/resumable-uploads/#requirements) | -| Stream cannot fetch an imported video | [Upload via link requirements](https://developers.cloudflare.com/stream/uploading-videos/upload-via-link/) | -| Upload finished but video is not playable | [Upload progress tracking](https://developers.cloudflare.com/stream/uploading-videos/direct-creator-uploads/#track-upload-progress) and [processing error codes](https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/#error-codes) | -| Private playback or embedding fails | [Signed tokens, restrictions, and allowed origins](https://developers.cloudflare.com/stream/viewing-videos/securing-your-stream/) | -| Webhook signature verification fails | [Verify the raw body and signature](https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/#verify-webhook-authenticity) | -| A custom HLS/DASH player behaves incorrectly | [Own-player integration and manifest handling](https://developers.cloudflare.com/stream/viewing-videos/using-own-player/) | -| Live stream will not connect or playback buffers | [Live troubleshooting](https://developers.cloudflare.com/stream/stream-live/troubleshooting/) | -| Simulcast output does not behave as expected | [Output configuration and limits](https://developers.cloudflare.com/stream/stream-live/simulcasting/) | -| Estimate storage or delivery costs | [Stream pricing](https://developers.cloudflare.com/stream/pricing/) | +| Unsupported file or upload constraint failure | [Supported formats and upload requirements](https://developers.cloudflare.com/stream/uploading-videos/index.md) | +| Large upload fails or restarts after interruption | [Resumable upload requirements](https://developers.cloudflare.com/stream/uploading-videos/resumable-uploads/index.md#requirements) | +| Stream cannot fetch an imported video | [Upload via link requirements](https://developers.cloudflare.com/stream/uploading-videos/upload-via-link/index.md) | +| Upload finished but video is not playable | [Upload progress tracking](https://developers.cloudflare.com/stream/uploading-videos/direct-creator-uploads/index.md#track-upload-progress) and [processing error codes](https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/index.md#error-codes) | +| Private playback or embedding fails | [Signed tokens, restrictions, and allowed origins](https://developers.cloudflare.com/stream/viewing-videos/securing-your-stream/index.md) | +| Webhook signature verification fails | [Verify the raw body and signature](https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/index.md#verify-webhook-authenticity) | +| A custom HLS/DASH player behaves incorrectly | [Own-player integration and manifest handling](https://developers.cloudflare.com/stream/viewing-videos/using-own-player/index.md) | +| Live stream will not connect or playback buffers | [Live troubleshooting](https://developers.cloudflare.com/stream/stream-live/troubleshooting/index.md) | +| Simulcast output does not behave as expected | [Output configuration and limits](https://developers.cloudflare.com/stream/stream-live/simulcasting/index.md) | +| Estimate storage or delivery costs | [Stream pricing](https://developers.cloudflare.com/stream/pricing/index.md) | Check the actual response and current docs before adopting a retry policy or treating a video as ready. Keep credentials out of browser code and logs; use [configuration.md](./configuration.md) for access setup and [patterns.md](./patterns.md) for state decisions. diff --git a/external/develop/devops/cloudflare/references/stream/patterns.md b/external/develop/devops/cloudflare/references/stream/patterns.md index 097952d..91de4ec 100644 --- a/external/develop/devops/cloudflare/references/stream/patterns.md +++ b/external/develop/devops/cloudflare/references/stream/patterns.md @@ -4,13 +4,13 @@ Use the official workflow examples after identifying where the existing applicat | Workflow | Read | |---|---| -| Browser uploads through a server-issued URL | [Direct creator uploads](https://developers.cloudflare.com/stream/uploading-videos/direct-creator-uploads/) | -| Large or interruption-prone uploads | [Resumable uploads](https://developers.cloudflare.com/stream/uploading-videos/resumable-uploads/) | -| Update application state when processing completes | [Video webhooks](https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/) and [upload progress tracking](https://developers.cloudflare.com/stream/uploading-videos/direct-creator-uploads/#track-upload-progress) | -| Verify an incoming processing notification | [Webhook authenticity](https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/#verify-webhook-authenticity) | -| Embed playback in a React application | [Stream Player and framework integrations](https://developers.cloudflare.com/stream/viewing-videos/using-the-stream-player/) | -| Serve private videos | [Signed playback and signing examples](https://developers.cloudflare.com/stream/viewing-videos/securing-your-stream/) | -| Broadcast from a browser | [Browser-based WebRTC](https://developers.cloudflare.com/stream/examples/browser-based-webrtc/) | +| Browser uploads through a server-issued URL | [Direct creator uploads](https://developers.cloudflare.com/stream/uploading-videos/direct-creator-uploads/index.md) | +| Large or interruption-prone uploads | [Resumable uploads](https://developers.cloudflare.com/stream/uploading-videos/resumable-uploads/index.md) | +| Update application state when processing completes | [Video webhooks](https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/index.md) and [upload progress tracking](https://developers.cloudflare.com/stream/uploading-videos/direct-creator-uploads/index.md#track-upload-progress) | +| Verify an incoming processing notification | [Webhook authenticity](https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/index.md#verify-webhook-authenticity) | +| Embed playback in a React application | [Stream Player and framework integrations](https://developers.cloudflare.com/stream/viewing-videos/using-the-stream-player/index.md) | +| Serve private videos | [Signed playback and signing examples](https://developers.cloudflare.com/stream/viewing-videos/securing-your-stream/index.md) | +| Broadcast from a browser | [Browser-based WebRTC](https://developers.cloudflare.com/stream/examples/browser-based-webrtc/index.md) | ## Application Decisions diff --git a/external/develop/devops/cloudflare/references/tail-workers/README.md b/external/develop/devops/cloudflare/references/tail-workers/README.md index 0388a57..56aaa70 100644 --- a/external/develop/devops/cloudflare/references/tail-workers/README.md +++ b/external/develop/devops/cloudflare/references/tail-workers/README.md @@ -4,10 +4,10 @@ Use Tail Workers when execution events need custom processing. Fetch the current | Task | Documentation | | --- | --- | -| Decide whether custom processing is needed | [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/) | -| Export logs and traces to an observability destination | [Exporting OpenTelemetry Data](https://developers.cloudflare.com/workers/observability/exporting-opentelemetry-data/) | -| Inspect a deployment interactively | [Real-time logs](https://developers.cloudflare.com/workers/observability/logs/real-time-logs/) | -| Implement the consumer | [Tail handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/) | +| Decide whether custom processing is needed | [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/index.md) | +| Export logs and traces to an observability destination | [OpenTelemetry export](https://developers.cloudflare.com/observability/export/opentelemetry/index.md) | +| Inspect a deployment interactively | [Real-time logs](https://developers.cloudflare.com/workers/observability/logs/real-time-logs/index.md) | +| Implement the consumer | [Tail handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/index.md) | Before adding a Tail Worker, check whether built-in OpenTelemetry export meets the destination’s needs. Use the Tail Workers guide for the tradeoff, then identify the custom filtering or transformation that remains necessary. diff --git a/external/develop/devops/cloudflare/references/tail-workers/api.md b/external/develop/devops/cloudflare/references/tail-workers/api.md index 840a961..44df5e9 100644 --- a/external/develop/devops/cloudflare/references/tail-workers/api.md +++ b/external/develop/devops/cloudflare/references/tail-workers/api.md @@ -4,10 +4,10 @@ Fetch the handler reference for current event shapes and language examples inste | Task | Documentation | | --- | --- | -| Implement the handler and understand asynchronous processing | [Tail handler syntax](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/#syntax) | -| Interpret execution outcomes, logs, exceptions, and timestamps | [Tail handler event reference](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/) | -| Inspect request fields and redaction behavior | [TailRequest](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/#tailrequest) | -| Interpret events from dynamic dispatch and user Workers | [Handler parameters](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/#parameters) | -| Write aggregated metrics | [Analytics Engine from Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/#use-analytics-engine-for-aggregated-metrics) | +| Implement the handler and understand asynchronous processing | [Tail handler syntax](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/index.md#syntax) | +| Interpret execution outcomes, logs, exceptions, and timestamps | [Tail handler event reference](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/index.md) | +| Inspect request fields and redaction behavior | [TailRequest](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/index.md#tailrequest) | +| Interpret events from dynamic dispatch and user Workers | [Handler parameters](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/index.md#parameters) | +| Write aggregated metrics | [Analytics Engine from Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/index.md#use-analytics-engine-for-aggregated-metrics) | Choose the event fields required by the destination and preserve the distinction between execution outcome and HTTP response status. Review what data may be retained before bypassing redaction; the documented heuristics are not a complete application privacy policy. See [patterns.md](./patterns.md) for filter design. diff --git a/external/develop/devops/cloudflare/references/tail-workers/configuration.md b/external/develop/devops/cloudflare/references/tail-workers/configuration.md index 2240707..de151b1 100644 --- a/external/develop/devops/cloudflare/references/tail-workers/configuration.md +++ b/external/develop/devops/cloudflare/references/tail-workers/configuration.md @@ -4,12 +4,12 @@ Identify the producer Worker and consumer Worker as separate deployment resource | Task | Documentation | | --- | --- | -| Connect a producer to a Tail Worker | [Configure Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/#configure-tail-workers) | -| Configure environments and resource bindings | [Wrangler configuration](https://developers.cloudflare.com/workers/wrangler/configuration/) | -| Configure destination credentials | [Secrets](https://developers.cloudflare.com/workers/configuration/secrets/) | -| Use built-in telemetry destinations instead | [Exporting OpenTelemetry Data](https://developers.cloudflare.com/workers/observability/exporting-opentelemetry-data/) | -| Check availability and billing model | [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/) | -| Check runtime capacity | [Workers limits](https://developers.cloudflare.com/workers/platform/limits/) | -| Check the handler required by the consumer | [Tail handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/) | +| Connect a producer to a Tail Worker | [Configure Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/index.md#configure-tail-workers) | +| Configure environments and resource bindings | [Wrangler configuration](https://developers.cloudflare.com/workers/wrangler/configuration/index.md) | +| Configure destination credentials | [Secrets](https://developers.cloudflare.com/workers/configuration/secrets/index.md) | +| Use built-in telemetry destinations instead | [OpenTelemetry export](https://developers.cloudflare.com/observability/export/opentelemetry/index.md) | +| Check availability and billing model | [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/index.md) | +| Check runtime capacity | [Workers limits](https://developers.cloudflare.com/workers/platform/limits/index.md) | +| Check the handler required by the consumer | [Tail handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/index.md) | Confirm which project owns each deployment and which environment the destination belongs to. Verify a known producer request reaches the intended destination before expanding coverage. See [api.md](./api.md) for payload handling and [gotchas.md](./gotchas.md) for investigation. diff --git a/external/develop/devops/cloudflare/references/tail-workers/gotchas.md b/external/develop/devops/cloudflare/references/tail-workers/gotchas.md index 07acced..bcdbcd3 100644 --- a/external/develop/devops/cloudflare/references/tail-workers/gotchas.md +++ b/external/develop/devops/cloudflare/references/tail-workers/gotchas.md @@ -4,12 +4,12 @@ Trace one known producer invocation through consumer execution and destination r | Task | Documentation | | --- | --- | -| Consumer receives no events or producer setup fails | [Configure Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/#configure-tail-workers) | -| Async processing or event interpretation is incorrect | [Tail handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/) | -| Filters confuse HTTP responses with execution failures | [TailItems](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/#tailitems) | -| Request data is missing or unexpectedly retained | [TailRequest](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/#tailrequest) | -| Inspect producer and consumer execution | [Real-time logs](https://developers.cloudflare.com/workers/observability/logs/real-time-logs/) | -| Reconsider a custom exporter at higher volume | [Exporting OpenTelemetry Data](https://developers.cloudflare.com/workers/observability/exporting-opentelemetry-data/) | +| Consumer receives no events or producer setup fails | [Configure Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/index.md#configure-tail-workers) | +| Async processing or event interpretation is incorrect | [Tail handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/index.md) | +| Filters confuse HTTP responses with execution failures | [TailItems](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/index.md#tailitems) | +| Request data is missing or unexpectedly retained | [TailRequest](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/index.md#tailrequest) | +| Inspect producer and consumer execution | [Real-time logs](https://developers.cloudflare.com/workers/observability/logs/real-time-logs/index.md) | +| Reconsider a custom exporter at higher volume | [OpenTelemetry export](https://developers.cloudflare.com/observability/export/opentelemetry/index.md) | Verify producer configuration, consumer identity, and destination credentials independently. Include both handled error responses and thrown exceptions when checking filters. Make destination failures observable without exposing the payload or credentials in diagnostic logs. Do not infer retention, retries, or batch guarantees from an example. diff --git a/external/develop/devops/cloudflare/references/tail-workers/patterns.md b/external/develop/devops/cloudflare/references/tail-workers/patterns.md index 66ec867..acd853d 100644 --- a/external/develop/devops/cloudflare/references/tail-workers/patterns.md +++ b/external/develop/devops/cloudflare/references/tail-workers/patterns.md @@ -4,11 +4,11 @@ Define the destination contract and required event coverage before writing trans | Task | Documentation | | --- | --- | -| Export supported telemetry without a custom consumer | [Exporting OpenTelemetry Data](https://developers.cloudflare.com/workers/observability/exporting-opentelemetry-data/) | -| Process and forward custom execution events | [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/) | -| Aggregate metrics rather than retain individual events | [Analytics Engine from Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/#use-analytics-engine-for-aggregated-metrics) | -| Choose filters based on execution and request fields | [Tail handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/) | -| Review sensitive request data handling | [TailRequest](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/#tailrequest) | +| Export supported telemetry without a custom consumer | [OpenTelemetry export](https://developers.cloudflare.com/observability/export/opentelemetry/index.md) | +| Process and forward custom execution events | [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/index.md) | +| Aggregate metrics rather than retain individual events | [Analytics Engine from Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/index.md#use-analytics-engine-for-aggregated-metrics) | +| Choose filters based on execution and request fields | [Tail handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/index.md) | +| Review sensitive request data handling | [TailRequest](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/index.md#tailrequest) | Decide which producers, routes, failures, and successful requests each destination needs. If sampling is appropriate, specify how it affects the questions the data must answer. Minimize retained fields, test transformation and serialization against representative events, and check destination rejection behavior. diff --git a/external/develop/devops/cloudflare/references/terraform/README.md b/external/develop/devops/cloudflare/references/terraform/README.md deleted file mode 100644 index ee913a3..0000000 --- a/external/develop/devops/cloudflare/references/terraform/README.md +++ /dev/null @@ -1,102 +0,0 @@ -# Cloudflare Terraform Provider - -**Expert guidance for Cloudflare Terraform Provider - infrastructure as code for Cloudflare resources.** - -## Core Principles - -- **Provider-first**: Use Terraform provider for ALL infrastructure - never mix with wrangler.jsonc for the same resources -- **State management**: Always use remote state (S3, Terraform Cloud, etc.) for team environments -- **Modular architecture**: Create reusable modules for common patterns (zones, workers, pages) -- **Version pinning**: Always pin provider version with `~>` for predictable upgrades -- **Secret management**: Use variables + environment vars for sensitive data - never hardcode API tokens - -## Provider Version - -| Version | Status | Notes | -|---------|--------|-------| -| 5.x | Current | Auto-generated from OpenAPI, breaking changes from v4 | -| 4.x | Legacy | Manual maintenance, deprecated | - -**Critical:** v5 renamed many resources (`cloudflare_record` → `cloudflare_dns_record`, `cloudflare_worker_*` → `cloudflare_workers_*`). See [gotchas.md](./gotchas.md#v5-breaking-changes) for migration details. - -## Provider Setup - -### Basic Configuration - -```hcl -terraform { - required_version = ">= 1.0" - - required_providers { - cloudflare = { - source = "cloudflare/cloudflare" - version = "~> 5.15.0" - } - } -} - -provider "cloudflare" { - api_token = var.cloudflare_api_token # or CLOUDFLARE_API_TOKEN env var -} -``` - -### Authentication Methods (priority order) - -1. **API Token** (RECOMMENDED): `api_token` or `CLOUDFLARE_API_TOKEN` - - Create: Dashboard → My Profile → API Tokens - - Scope to specific accounts/zones for security - -2. **Global API Key** (LEGACY): `api_key` + `api_email` or `CLOUDFLARE_API_KEY` + `CLOUDFLARE_EMAIL` - - Less secure, use tokens instead - -3. **User Service Key**: `user_service_key` for Origin CA certificates - - - -## Quick Reference: Common Commands - -```bash -terraform init # Initialize provider -terraform plan # Plan changes -terraform apply # Apply changes -terraform destroy # Destroy resources -terraform import cloudflare_zone.example # Import existing -terraform state list # List resources in state -terraform output # Show outputs -terraform fmt -recursive # Format code -terraform validate # Validate configuration -``` - -## Import Existing Resources - -Use cf-terraforming to generate configs from existing Cloudflare resources: - -```bash -# Install -brew install cloudflare/cloudflare/cf-terraforming - -# Generate HCL from existing resources -cf-terraforming generate --resource-type cloudflare_dns_record --zone - -# Import into Terraform state -cf-terraforming import --resource-type cloudflare_dns_record --zone -``` - -## Reading Order - -1. Start with [README.md](./README.md) for provider setup and authentication -2. Review [configuration.md](./configuration.md) for resource configurations -3. Check [api.md](./api.md) for data sources and existing resource queries -4. See [patterns.md](./patterns.md) for multi-environment and CI/CD patterns -5. Read [gotchas.md](./gotchas.md) for state drift, v5 breaking changes, and troubleshooting - -## In This Reference -- [configuration.md](./configuration.md) - Resources for zones, DNS, workers, KV, R2, D1, Pages, rulesets -- [api.md](./api.md) - Data sources for existing resources -- [patterns.md](./patterns.md) - Architecture patterns, multi-env setup, CI/CD integration -- [gotchas.md](./gotchas.md) - Common issues, security, best practices - -## See Also -- [pulumi](../pulumi/) - Alternative IaC tool for Cloudflare -- [wrangler](https://developers.cloudflare.com/workers/wrangler/) - CLI deployment alternative -- [workers](https://developers.cloudflare.com/workers/) - Worker runtime documentation diff --git a/external/develop/devops/cloudflare/references/terraform/api.md b/external/develop/devops/cloudflare/references/terraform/api.md deleted file mode 100644 index 8a06c1c..0000000 --- a/external/develop/devops/cloudflare/references/terraform/api.md +++ /dev/null @@ -1,178 +0,0 @@ -# Terraform Data Sources Reference - -Query existing Cloudflare resources to reference in your configurations. - -## v5 Data Source Names - -| v4 Name | v5 Name | Notes | -|---------|---------|-------| -| `cloudflare_record` | `cloudflare_dns_record` | | -| `cloudflare_worker_script` | `cloudflare_workers_script` | Note: plural | -| `cloudflare_access_*` | `cloudflare_zero_trust_*` | Access → Zero Trust | - -## Zone Data Sources - -```hcl -# Get zone by name -data "cloudflare_zone" "example" { - name = "example.com" -} - -# Use in resources -resource "cloudflare_dns_record" "www" { - zone_id = data.cloudflare_zone.example.id - name = "www" - # ... -} -``` - -## Account Data Sources - -```hcl -# List all accounts -data "cloudflare_accounts" "main" { - name = "My Account" -} - -# Use account ID -resource "cloudflare_worker_script" "api" { - account_id = data.cloudflare_accounts.main.accounts[0].id - # ... -} -``` - -## Worker Data Sources - -```hcl -# Get existing worker script (v5: cloudflare_workers_script) -data "cloudflare_workers_script" "existing" { - account_id = var.account_id - name = "existing-worker" -} - -# Reference in service bindings -resource "cloudflare_workers_script" "consumer" { - service_binding { - name = "UPSTREAM" - service = data.cloudflare_workers_script.existing.name - } -} -``` - -## KV Data Sources - -```hcl -# Get KV namespace -data "cloudflare_workers_kv_namespace" "existing" { - account_id = var.account_id - namespace_id = "abc123" -} - -# Use in worker binding -resource "cloudflare_workers_script" "api" { - kv_namespace_binding { - name = "KV" - namespace_id = data.cloudflare_workers_kv_namespace.existing.id - } -} -``` - -## Lists Data Source - -```hcl -# Get IP lists for WAF rules -data "cloudflare_list" "blocked_ips" { - account_id = var.account_id - name = "blocked_ips" -} -``` - -## IP Ranges Data Source - -```hcl -# Get Cloudflare IP ranges (for firewall rules) -data "cloudflare_ip_ranges" "cloudflare" {} - -output "ipv4_cidrs" { - value = data.cloudflare_ip_ranges.cloudflare.ipv4_cidr_blocks -} - -output "ipv6_cidrs" { - value = data.cloudflare_ip_ranges.cloudflare.ipv6_cidr_blocks -} - -# Use in security group rules (AWS example) -resource "aws_security_group_rule" "allow_cloudflare" { - type = "ingress" - from_port = 443 - to_port = 443 - protocol = "tcp" - cidr_blocks = data.cloudflare_ip_ranges.cloudflare.ipv4_cidr_blocks - security_group_id = aws_security_group.web.id -} -``` - -## Common Patterns - -### Import ID Formats - -| Resource | Import ID Format | -|----------|------------------| -| `cloudflare_zone` | `` | -| `cloudflare_dns_record` | `/` | -| `cloudflare_workers_script` | `/` | -| `cloudflare_workers_kv_namespace` | `/` | -| `cloudflare_r2_bucket` | `/` | -| `cloudflare_d1_database` | `/` | -| `cloudflare_pages_project` | `/` | - -```bash -# Example: Import DNS record -terraform import cloudflare_dns_record.example / -``` - -### Reference Across Modules - -```hcl -# modules/worker/main.tf -data "cloudflare_zone" "main" { - name = var.domain -} - -resource "cloudflare_worker_route" "api" { - zone_id = data.cloudflare_zone.main.id - pattern = "api.${var.domain}/*" - script_name = cloudflare_worker_script.api.name -} -``` - -### Output Important Values - -```hcl -output "zone_id" { - value = cloudflare_zone.main.id - description = "Zone ID for DNS management" -} - -output "worker_url" { - value = "https://${cloudflare_worker_domain.api.hostname}" - description = "Worker API endpoint" -} - -output "kv_namespace_id" { - value = cloudflare_workers_kv_namespace.app.id - sensitive = false -} - -output "name_servers" { - value = cloudflare_zone.main.name_servers - description = "Name servers for domain registration" -} -``` - -## See Also - -- [README](./README.md) - Provider setup -- [Configuration Reference](./configuration.md) - All resource types -- [Patterns](./patterns.md) - Architecture patterns -- [Troubleshooting](./gotchas.md) - Common issues diff --git a/external/develop/devops/cloudflare/references/terraform/configuration.md b/external/develop/devops/cloudflare/references/terraform/configuration.md deleted file mode 100644 index 4b5eeb5..0000000 --- a/external/develop/devops/cloudflare/references/terraform/configuration.md +++ /dev/null @@ -1,197 +0,0 @@ -# Terraform Configuration Reference - -Complete resource configurations for Cloudflare infrastructure. - -## Zone & DNS - -```hcl -# Zone + settings -resource "cloudflare_zone" "example" { account = { id = var.account_id }; name = "example.com"; type = "full" } -resource "cloudflare_zone_settings_override" "example" { - zone_id = cloudflare_zone.example.id - settings { ssl = "strict"; always_use_https = "on"; min_tls_version = "1.2"; tls_1_3 = "on"; http3 = "on" } -} - -# DNS records (A, CNAME, MX, TXT) -resource "cloudflare_dns_record" "www" { - zone_id = cloudflare_zone.example.id; name = "www"; content = "192.0.2.1"; type = "A"; proxied = true -} -resource "cloudflare_dns_record" "mx" { - for_each = { "10" = "mail1.example.com", "20" = "mail2.example.com" } - zone_id = cloudflare_zone.example.id; name = "@"; content = each.value; type = "MX"; priority = each.key -} -``` - -## Workers - -### Simple Pattern (Legacy - Still Works) - -```hcl -resource "cloudflare_workers_script" "api" { - account_id = var.account_id; name = "api-worker"; content = file("worker.js") - module = true; compatibility_date = "2025-01-01" - kv_namespace_binding { name = "KV"; namespace_id = cloudflare_workers_kv_namespace.cache.id } - r2_bucket_binding { name = "BUCKET"; bucket_name = cloudflare_r2_bucket.assets.name } - d1_database_binding { name = "DB"; database_id = cloudflare_d1_database.app.id } - secret_text_binding { name = "SECRET"; text = var.secret } -} -``` - -### Gradual Rollouts (Recommended for Production) - -```hcl -resource "cloudflare_worker" "api" { account_id = var.account_id; name = "api-worker" } -resource "cloudflare_worker_version" "api_v1" { - account_id = var.account_id; worker_name = cloudflare_worker.api.name - content = file("worker.js"); content_sha256 = filesha256("worker.js") - compatibility_date = "2025-01-01" - bindings { - kv_namespace { name = "KV"; namespace_id = cloudflare_workers_kv_namespace.cache.id } - r2_bucket { name = "BUCKET"; bucket_name = cloudflare_r2_bucket.assets.name } - } -} -resource "cloudflare_workers_deployment" "api" { - account_id = var.account_id; worker_name = cloudflare_worker.api.name - versions { version_id = cloudflare_worker_version.api_v1.id; percentage = 100 } -} -``` - -### Worker Binding Types (v5) - -| Binding | Attribute | Example | -|---------|-----------|---------| -| KV | `kv_namespace_binding` | `{ name = "KV", namespace_id = "..." }` | -| R2 | `r2_bucket_binding` | `{ name = "BUCKET", bucket_name = "..." }` | -| D1 | `d1_database_binding` | `{ name = "DB", database_id = "..." }` | -| Service | `service_binding` | `{ name = "AUTH", service = "auth-worker" }` | -| Secret | `secret_text_binding` | `{ name = "API_KEY", text = "..." }` | -| Queue | `queue_binding` | `{ name = "QUEUE", queue_name = "..." }` | -| Vectorize | `vectorize_binding` | `{ name = "INDEX", index_name = "..." }` | -| Hyperdrive | `hyperdrive_binding` | `{ name = "DB", id = "..." }` | -| AI | `ai_binding` | `{ name = "AI" }` | -| Browser | `browser_binding` | `{ name = "BROWSER" }` | -| Analytics | `analytics_engine_binding` | `{ name = "ANALYTICS", dataset = "..." }` | -| mTLS | `mtls_certificate_binding` | `{ name = "CERT", certificate_id = "..." }` | - -### Routes & Triggers - -```hcl -resource "cloudflare_worker_route" "api" { - zone_id = cloudflare_zone.example.id; pattern = "api.example.com/*" - script_name = cloudflare_workers_script.api.name -} -resource "cloudflare_worker_cron_trigger" "task" { - account_id = var.account_id; script_name = cloudflare_workers_script.api.name - schedules = ["*/5 * * * *"] -} -``` - -## Storage (KV, R2, D1) - -```hcl -# KV -resource "cloudflare_workers_kv_namespace" "cache" { account_id = var.account_id; title = "cache" } -resource "cloudflare_workers_kv" "config" { - account_id = var.account_id; namespace_id = cloudflare_workers_kv_namespace.cache.id - key_name = "config"; value = jsonencode({ version = "1.0" }) -} - -# R2 -resource "cloudflare_r2_bucket" "assets" { account_id = var.account_id; name = "assets"; location = "WNAM" } - -# D1 (migrations via wrangler) & Queues -resource "cloudflare_d1_database" "app" { account_id = var.account_id; name = "app-db" } -resource "cloudflare_queue" "events" { account_id = var.account_id; name = "events-queue" } -``` - -## Pages - -```hcl -resource "cloudflare_pages_project" "site" { - account_id = var.account_id; name = "site"; production_branch = "main" - deployment_configs { - production { - compatibility_date = "2025-01-01" - environment_variables = { NODE_ENV = "production" } - kv_namespaces = { KV = cloudflare_workers_kv_namespace.cache.id } - d1_databases = { DB = cloudflare_d1_database.app.id } - } - } - build_config { build_command = "npm run build"; destination_dir = "dist" } - source { type = "github"; config { owner = "org"; repo_name = "site"; production_branch = "main" }} -} - -resource "cloudflare_pages_domain" "custom" { - account_id = var.account_id; project_name = cloudflare_pages_project.site.name; domain = "site.example.com" -} -``` - -## Rulesets (WAF, Redirects, Cache) - -```hcl -# WAF -resource "cloudflare_ruleset" "waf" { - zone_id = cloudflare_zone.example.id; name = "WAF"; kind = "zone"; phase = "http_request_firewall_custom" - rules { action = "block"; enabled = true; expression = "(cf.client.bot) and not (cf.verified_bot)" } -} - -# Redirects -resource "cloudflare_ruleset" "redirects" { - zone_id = cloudflare_zone.example.id; name = "Redirects"; kind = "zone"; phase = "http_request_dynamic_redirect" - rules { - action = "redirect"; enabled = true; expression = "(http.request.uri.path eq \"/old\")" - action_parameters { from_value { status_code = 301; target_url { value = "https://example.com/new" }}} - } -} - -# Cache rules -resource "cloudflare_ruleset" "cache" { - zone_id = cloudflare_zone.example.id; name = "Cache"; kind = "zone"; phase = "http_request_cache_settings" - rules { - action = "set_cache_settings"; enabled = true; expression = "(http.request.uri.path matches \"\\.(jpg|png|css|js)$\")" - action_parameters { cache = true; edge_ttl { mode = "override_origin"; default = 86400 }} - } -} -``` - -## Load Balancers - -```hcl -resource "cloudflare_load_balancer_monitor" "http" { - account_id = var.account_id; type = "http"; path = "/health"; interval = 60; timeout = 5 -} -resource "cloudflare_load_balancer_pool" "api" { - account_id = var.account_id; name = "api-pool"; monitor = cloudflare_load_balancer_monitor.http.id - origins { name = "api-1"; address = "192.0.2.1" } - origins { name = "api-2"; address = "192.0.2.2" } -} -resource "cloudflare_load_balancer" "api" { - zone_id = cloudflare_zone.example.id; name = "api.example.com" - default_pool_ids = [cloudflare_load_balancer_pool.api.id]; steering_policy = "geo" -} -``` - -## Access (Zero Trust) - -```hcl -resource "cloudflare_access_application" "admin" { - account_id = var.account_id; name = "Admin"; domain = "admin.example.com"; type = "self_hosted" - session_duration = "24h"; allowed_idps = [cloudflare_access_identity_provider.github.id] -} -resource "cloudflare_access_policy" "allow" { - account_id = var.account_id; application_id = cloudflare_access_application.admin.id - name = "Allow"; decision = "allow"; precedence = 1 - include { email = ["admin@example.com"] } -} -resource "cloudflare_access_identity_provider" "github" { - account_id = var.account_id; name = "GitHub"; type = "github" - config { client_id = var.github_id; client_secret = var.github_secret } -} -``` - -## See Also - -- [README](./README.md) - Provider setup -- [API](./api.md) - Data sources -- [Patterns](./patterns.md) - Use cases -- [Troubleshooting](./gotchas.md) - Issues diff --git a/external/develop/devops/cloudflare/references/terraform/gotchas.md b/external/develop/devops/cloudflare/references/terraform/gotchas.md deleted file mode 100644 index eb4731d..0000000 --- a/external/develop/devops/cloudflare/references/terraform/gotchas.md +++ /dev/null @@ -1,150 +0,0 @@ -# Terraform Troubleshooting & Best Practices - -Common issues, security considerations, and best practices. - -## State Drift Issues - -Some resources have known state drift. Add lifecycle blocks to prevent perpetual diffs: - -| Resource | Drift Attributes | Workaround | -|----------|------------------|------------| -| `cloudflare_pages_project` | `deployment_configs.*` | `ignore_changes = [deployment_configs]` | -| `cloudflare_workers_script` | secrets returned as REDACTED | `ignore_changes = [secret_text_binding]` | -| `cloudflare_load_balancer` | `adaptive_routing`, `random_steering` | `ignore_changes = [adaptive_routing, random_steering]` | -| `cloudflare_workers_kv` | special chars in keys (< 5.16.0) | Upgrade to 5.16.0+ | - -```hcl -# Example: Ignore secret drift -resource "cloudflare_workers_script" "api" { - account_id = var.account_id - name = "api-worker" - content = file("worker.js") - secret_text_binding { name = "API_KEY"; text = var.api_key } - - lifecycle { - ignore_changes = [secret_text_binding] - } -} -``` - -## v5 Breaking Changes - -Provider v5 is current (auto-generated from OpenAPI). v4→v5 has breaking changes: - -**Resource Renames:** - -| v4 Resource | v5 Resource | Notes | -|-------------|-------------|-------| -| `cloudflare_record` | `cloudflare_dns_record` | | -| `cloudflare_worker_script` | `cloudflare_workers_script` | Note: plural | -| `cloudflare_worker_*` | `cloudflare_workers_*` | All worker resources | -| `cloudflare_access_*` | `cloudflare_zero_trust_*` | Access → Zero Trust | - -**Attribute Changes:** - -| v4 Attribute | v5 Attribute | Resources | -|--------------|--------------|-----------| -| `zone` | `name` | zone | -| `account_id` | `account.id` | zone (object syntax) | -| `key` | `key_name` | KV | -| `location_hint` | `location` | R2 | - -**State Migration:** - -```bash -# Rename resources in state after v5 upgrade -terraform state mv cloudflare_record.example cloudflare_dns_record.example -terraform state mv cloudflare_worker_script.api cloudflare_workers_script.api -``` - -## Resource-Specific Gotchas - -### R2 Location Case Sensitivity - -**Problem:** Terraform creates R2 bucket but fails on subsequent applies -**Cause:** Location must be UPPERCASE -**Solution:** Use `WNAM`, `ENAM`, `WEUR`, `EEUR`, `APAC` (not `wnam`, `enam`, etc.) - -```hcl -resource "cloudflare_r2_bucket" "assets" { - account_id = var.account_id - name = "assets" - location = "WNAM" # UPPERCASE required -} -``` - -### KV Special Characters (< 5.16.0) - -**Problem:** Keys with `+`, `#`, `%` cause encoding issues -**Cause:** URL encoding bug in provider < 5.16.0 -**Solution:** Upgrade to 5.16.0+ or avoid special chars in keys - -### D1 Migrations - -**Problem:** Terraform creates database but schema is empty -**Cause:** Terraform only creates D1 resource, not schema -**Solution:** Run migrations via wrangler after Terraform apply - -```bash -# After terraform apply -wrangler d1 migrations apply -``` - -### Worker Script Size Limit - -**Problem:** Worker deployment fails with "script too large" -**Cause:** Worker script + dependencies exceed 10 MB limit -**Solution:** Use code splitting, external dependencies, or minification - -### Pages Project Drift - -**Problem:** Pages project shows perpetual diff on `deployment_configs` -**Cause:** Cloudflare API adds default values not in Terraform state -**Solution:** Add lifecycle ignore block (see State Drift table above) - -## Common Errors - -### "Error: couldn't find resource" - -**Cause:** Resource was deleted outside Terraform -**Solution:** Import resource back into state with `terraform import cloudflare_zone.example ` or remove from state with `terraform state rm cloudflare_zone.example` - -### "409 Conflict on worker deployment" - -**Cause:** Worker being deployed by both Terraform and wrangler simultaneously -**Solution:** Choose one deployment method; if using Terraform, remove wrangler deployments - -### "DNS record already exists" - -**Cause:** Existing DNS record not imported into Terraform state -**Solution:** Find record ID in Cloudflare dashboard and import with `terraform import cloudflare_dns_record.example /` - -### "Invalid provider configuration" - -**Cause:** API token missing, invalid, or lacking required permissions -**Solution:** Set `CLOUDFLARE_API_TOKEN` environment variable or check token permissions in dashboard - -### "State locking errors" - -**Cause:** Multiple concurrent Terraform runs or stale lock from crashed process -**Solution:** Remove stale lock with `terraform force-unlock ` (use with caution) - -## Limits - -| Resource | Limit | Notes | -|----------|-------|-------| -| API token rate limit | Varies by plan | Use `api_client_logging = true` to debug -| Worker script size | 10 MB | Includes all dependencies -| KV keys per namespace | Unlimited | Pay per operation -| R2 storage | Unlimited | Pay per GB -| D1 databases | 50,000 per account | Free tier: 10 -| Pages projects | 500 per account | 100 for free accounts -| DNS records | 3,500 per zone | Free plan - -## See Also - -- [README](./README.md) - Provider setup -- [Configuration](./configuration.md) - Resources -- [API](./api.md) - Data sources -- [Patterns](./patterns.md) - Use cases -- Provider docs: https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs diff --git a/external/develop/devops/cloudflare/references/terraform/patterns.md b/external/develop/devops/cloudflare/references/terraform/patterns.md deleted file mode 100644 index aea3a96..0000000 --- a/external/develop/devops/cloudflare/references/terraform/patterns.md +++ /dev/null @@ -1,174 +0,0 @@ -# Terraform Patterns & Use Cases - -Architecture patterns, multi-environment setups, and real-world use cases. - -## Recommended Directory Structure - -``` -terraform/ -├── environments/ -│ ├── production/ -│ │ ├── main.tf -│ │ └── terraform.tfvars -│ └── staging/ -│ ├── main.tf -│ └── terraform.tfvars -├── modules/ -│ ├── zone/ -│ ├── worker/ -│ └── dns/ -└── shared/ # Shared resources across envs - └── main.tf -``` - -**Note:** Cloudflare recommends avoiding modules for provider resources due to v5 auto-generation complexity. Prefer environment directories + shared state instead. - -## Multi-Environment Setup - -```hcl -# Directory: environments/{production,staging}/main.tf + modules/{zone,worker,pages} -module "zone" { - source = "../../modules/zone"; account_id = var.account_id; zone_name = "example.com"; environment = "production" -} -module "api_worker" { - source = "../../modules/worker"; account_id = var.account_id; zone_id = module.zone.zone_id - name = "api-worker-prod"; script = file("../../workers/api.js"); environment = "production" -} -``` - -## R2 State Backend - -```hcl -terraform { - backend "s3" { - bucket = "terraform-state" - key = "cloudflare.tfstate" - region = "auto" - endpoints = { s3 = "https://.r2.cloudflarestorage.com" } - skip_credentials_validation = true - skip_region_validation = true - skip_requesting_account_id = true - skip_metadata_api_check = true - skip_s3_checksum = true - } -} -``` - -## Worker with All Bindings - -```hcl -locals { worker_name = "full-stack-worker" } -resource "cloudflare_workers_kv_namespace" "app" { account_id = var.account_id; title = "${local.worker_name}-kv" } -resource "cloudflare_r2_bucket" "app" { account_id = var.account_id; name = "${local.worker_name}-bucket" } -resource "cloudflare_d1_database" "app" { account_id = var.account_id; name = "${local.worker_name}-db" } - -resource "cloudflare_worker_script" "app" { - account_id = var.account_id; name = local.worker_name; content = file("worker.js"); module = true - compatibility_date = "2025-01-01" - kv_namespace_binding { name = "KV"; namespace_id = cloudflare_workers_kv_namespace.app.id } - r2_bucket_binding { name = "BUCKET"; bucket_name = cloudflare_r2_bucket.app.name } - d1_database_binding { name = "DB"; database_id = cloudflare_d1_database.app.id } - secret_text_binding { name = "API_KEY"; text = var.api_key } -} -``` - -## Wrangler Integration - -**CRITICAL**: Wrangler and Terraform must NOT manage same resources. - -**Terraform**: Zones, DNS, security rules, Access, load balancers, worker deployments (CI/CD), KV/R2/D1 resource creation -**Wrangler**: Local dev (`wrangler dev`), manual deploys, D1 migrations, KV bulk ops, log streaming (`wrangler tail`) - -### CI/CD Pattern - -```hcl -# Terraform creates infrastructure -resource "cloudflare_workers_kv_namespace" "app" { account_id = var.account_id; title = "app-kv" } -resource "cloudflare_d1_database" "app" { account_id = var.account_id; name = "app-db" } -output "kv_namespace_id" { value = cloudflare_workers_kv_namespace.app.id } -output "d1_database_id" { value = cloudflare_d1_database.app.id } -``` - -```yaml -# GitHub Actions: terraform apply → envsubst wrangler.jsonc.template → wrangler deploy -- run: terraform apply -auto-approve -- run: | - export KV_NAMESPACE_ID=$(terraform output -raw kv_namespace_id) - envsubst < wrangler.jsonc.template > wrangler.jsonc -- run: wrangler deploy -``` - -## Use Cases - -### Static Site + API Worker - -```hcl -resource "cloudflare_pages_project" "frontend" { - account_id = var.account_id; name = "frontend"; production_branch = "main" - build_config { build_command = "npm run build"; destination_dir = "dist" } -} -resource "cloudflare_worker_script" "api" { - account_id = var.account_id; name = "api"; content = file("api-worker.js") - d1_database_binding { name = "DB"; database_id = cloudflare_d1_database.api_db.id } -} -resource "cloudflare_dns_record" "frontend" { - zone_id = cloudflare_zone.main.id; name = "app"; content = cloudflare_pages_project.frontend.subdomain; type = "CNAME"; proxied = true -} -resource "cloudflare_worker_route" "api" { - zone_id = cloudflare_zone.main.id; pattern = "api.example.com/*"; script_name = cloudflare_worker_script.api.name -} -``` - -### Multi-Region Load Balancing - -```hcl -resource "cloudflare_load_balancer_pool" "us" { - account_id = var.account_id; name = "us-pool"; monitor = cloudflare_load_balancer_monitor.http.id - origins { name = "us-east"; address = var.us_east_ip } -} -resource "cloudflare_load_balancer_pool" "eu" { - account_id = var.account_id; name = "eu-pool"; monitor = cloudflare_load_balancer_monitor.http.id - origins { name = "eu-west"; address = var.eu_west_ip } -} -resource "cloudflare_load_balancer" "global" { - zone_id = cloudflare_zone.main.id; name = "api.example.com"; steering_policy = "geo" - default_pool_ids = [cloudflare_load_balancer_pool.us.id] - region_pools { region = "WNAM"; pool_ids = [cloudflare_load_balancer_pool.us.id] } - region_pools { region = "WEU"; pool_ids = [cloudflare_load_balancer_pool.eu.id] } -} -``` - -### Secure Admin with Access - -```hcl -resource "cloudflare_pages_project" "admin" { account_id = var.account_id; name = "admin"; production_branch = "main" } -resource "cloudflare_access_application" "admin" { - account_id = var.account_id; name = "Admin"; domain = "admin.example.com"; type = "self_hosted"; session_duration = "24h" - allowed_idps = [cloudflare_access_identity_provider.google.id] -} -resource "cloudflare_access_policy" "allow" { - account_id = var.account_id; application_id = cloudflare_access_application.admin.id - name = "Allow admins"; decision = "allow"; precedence = 1; include { email = var.admin_emails } -} -``` - -### Reusable Module - -```hcl -# modules/cloudflare-zone/main.tf -variable "account_id" { type = string }; variable "domain" { type = string }; variable "ssl_mode" { default = "strict" } -resource "cloudflare_zone" "main" { account = { id = var.account_id }; name = var.domain } -resource "cloudflare_zone_settings_override" "main" { - zone_id = cloudflare_zone.main.id; settings { ssl = var.ssl_mode; always_use_https = "on" } -} -output "zone_id" { value = cloudflare_zone.main.id } - -# Usage: module "prod" { source = "./modules/cloudflare-zone"; account_id = var.account_id; domain = "example.com" } -``` - -## See Also - -- [README](./README.md) - Provider setup -- [Configuration Reference](./configuration.md) - All resource types -- [API Reference](./api.md) - Data sources -- [Troubleshooting](./gotchas.md) - Best practices, common issues diff --git a/external/develop/devops/cloudflare/references/tunnel/README.md b/external/develop/devops/cloudflare/references/tunnel/README.md index f8cc2af..20427d3 100644 --- a/external/develop/devops/cloudflare/references/tunnel/README.md +++ b/external/develop/devops/cloudflare/references/tunnel/README.md @@ -4,10 +4,10 @@ Use Tunnel to connect origin services to Cloudflare. Inspect the existing tunnel | Task | Documentation | | --- | --- | -| Create a remotely-managed tunnel or a temporary development tunnel | [Setup](https://developers.cloudflare.com/tunnel/setup/) | -| Maintain a tunnel managed through local files | [Create a locally-managed tunnel](https://developers.cloudflare.com/tunnel/advanced/local-management/create-local-tunnel/) | -| Publish an application and check protocol requirements | [Routing](https://developers.cloudflare.com/tunnel/routing/) | -| Choose private networking, Workers VPC, or Access integration | [Integrations](https://developers.cloudflare.com/tunnel/integrations/) | +| Create a remotely-managed tunnel or a temporary development tunnel | [Setup](https://developers.cloudflare.com/tunnel/get-started/index.md) | +| Maintain a tunnel managed through local files | [Create a locally-managed tunnel](https://developers.cloudflare.com/tunnel/features/locally-managed-tunnels/create-local-tunnel/index.md) | +| Publish an application and check protocol requirements | [Routing](https://developers.cloudflare.com/tunnel/concepts/routing/index.md) | +| Choose private networking, Workers VPC, or Access integration | [Integrations](https://developers.cloudflare.com/tunnel/integrations/index.md) | Decide whether the goal is a public application, authenticated private access, or connectivity from a Worker. Then identify who owns configuration and how it will be deployed; multiple environments alone do not require local management. diff --git a/external/develop/devops/cloudflare/references/tunnel/api.md b/external/develop/devops/cloudflare/references/tunnel/api.md index e02ec55..6a1e48a 100644 --- a/external/develop/devops/cloudflare/references/tunnel/api.md +++ b/external/develop/devops/cloudflare/references/tunnel/api.md @@ -4,11 +4,11 @@ Fetch current operation schemas, permissions, and examples before automating tun | Task | Documentation | | --- | --- | -| Create a tunnel, configure ingress, and create application DNS through the API | [Setup](https://developers.cloudflare.com/tunnel/setup/) | -| List existing tunnels and inspect response fields | [List Cloudflare Tunnels API](https://developers.cloudflare.com/api/resources/zero_trust/subresources/tunnels/subresources/cloudflared/methods/list/) | -| Retrieve and rotate tunnel tokens | [Tunnel tokens](https://developers.cloudflare.com/tunnel/advanced/tunnel-tokens/) | -| Manage local tunnels using the CLI | [Useful commands](https://developers.cloudflare.com/tunnel/advanced/local-management/tunnel-useful-commands/) | -| Configure public DNS and routing behavior | [Routing](https://developers.cloudflare.com/tunnel/routing/) | -| Select private-network integration and its setup guide | [Integrations](https://developers.cloudflare.com/tunnel/integrations/) | +| Create a tunnel, configure ingress, and create application DNS through the API | [Setup](https://developers.cloudflare.com/tunnel/get-started/index.md) | +| List existing tunnels and inspect response fields | [List Cloudflare Tunnels API](https://developers.cloudflare.com/api/resources/zero_trust/subresources/tunnels/subresources/cloudflared/methods/list/index.md) | +| Retrieve and rotate tunnel tokens | [Tunnel tokens](https://developers.cloudflare.com/tunnel/reference/tunnel-tokens/index.md) | +| Manage local tunnels using the CLI | [Useful commands](https://developers.cloudflare.com/tunnel/features/locally-managed-tunnels/tunnel-useful-commands/index.md) | +| Configure public DNS and routing behavior | [Routing](https://developers.cloudflare.com/tunnel/concepts/routing/index.md) | +| Select private-network integration and its setup guide | [Integrations](https://developers.cloudflare.com/tunnel/integrations/index.md) | Identify the account, tunnel ID, and management mode before a write. Review existing routes before replacing configuration; distinguish tunnel lifecycle operations from DNS changes. Use [patterns.md](./patterns.md) when infrastructure as code owns these resources. diff --git a/external/develop/devops/cloudflare/references/tunnel/configuration.md b/external/develop/devops/cloudflare/references/tunnel/configuration.md index 3c17844..5caf7ee 100644 --- a/external/develop/devops/cloudflare/references/tunnel/configuration.md +++ b/external/develop/devops/cloudflare/references/tunnel/configuration.md @@ -4,11 +4,11 @@ Read the documentation for the existing management mode before changing routes o | Task | Documentation | | --- | --- | -| Configure remotely-managed tunnels | [Setup](https://developers.cloudflare.com/tunnel/setup/) | -| Edit local ingress rules, service mappings, and validate matching | [Configuration file](https://developers.cloudflare.com/tunnel/advanced/local-management/configuration-file/) | -| Configure origin TLS, HTTP, and connection behavior | [Origin parameters](https://developers.cloudflare.com/tunnel/advanced/origin-parameters/) | -| Configure runtime flags and service arguments | [Run parameters](https://developers.cloudflare.com/tunnel/advanced/run-parameters/) | -| Manage remote tunnel tokens and rotation | [Tunnel tokens](https://developers.cloudflare.com/tunnel/advanced/tunnel-tokens/) | -| Choose service protocols and DNS routing | [Routing](https://developers.cloudflare.com/tunnel/routing/) | +| Configure remotely-managed tunnels | [Setup](https://developers.cloudflare.com/tunnel/get-started/index.md) | +| Edit local ingress rules, service mappings, and validate matching | [Configuration file](https://developers.cloudflare.com/tunnel/features/locally-managed-tunnels/configuration-file/index.md) | +| Configure origin TLS, HTTP, and connection behavior | [Origin parameters](https://developers.cloudflare.com/tunnel/reference/origin-parameters/index.md) | +| Configure runtime flags and service arguments | [Run parameters](https://developers.cloudflare.com/tunnel/reference/run-parameters/index.md) | +| Manage remote tunnel tokens and rotation | [Tunnel tokens](https://developers.cloudflare.com/tunnel/reference/tunnel-tokens/index.md) | +| Choose service protocols and DNS routing | [Routing](https://developers.cloudflare.com/tunnel/concepts/routing/index.md) | Confirm which configuration source the running process uses, then review the routes affected by the change. Match origin settings to the actual service and certificate rather than copying settings from a different deployment. See [networking.md](./networking.md) for connectivity and [patterns.md](./patterns.md) for rollout decisions. diff --git a/external/develop/devops/cloudflare/references/tunnel/gotchas.md b/external/develop/devops/cloudflare/references/tunnel/gotchas.md index 84eaf9d..d331932 100644 --- a/external/develop/devops/cloudflare/references/tunnel/gotchas.md +++ b/external/develop/devops/cloudflare/references/tunnel/gotchas.md @@ -4,12 +4,12 @@ Capture the tunnel status, failing route, management mode, and cloudflared logs | Task | Documentation | | --- | --- | -| Tunnel fails to connect or reports an error | [Troubleshooting](https://developers.cloudflare.com/tunnel/troubleshooting/) | -| Tunnel is healthy but an HTTPS application fails or redirects | [HTTPS origins](https://developers.cloudflare.com/tunnel/troubleshooting/https-origins/) | -| Inspect connection health and application diagnostics | [Monitoring](https://developers.cloudflare.com/tunnel/monitoring/) | -| Check local configuration and rule matching | [Configuration file](https://developers.cloudflare.com/tunnel/advanced/local-management/configuration-file/) | -| Connections behave unexpectedly after token rotation | [Tunnel tokens](https://developers.cloudflare.com/tunnel/advanced/tunnel-tokens/) | -| Check replica capacity or firewall requirements | [Configuration](https://developers.cloudflare.com/tunnel/configuration/) | -| Update an existing installation | [Update cloudflared](https://developers.cloudflare.com/tunnel/downloads/update-cloudflared/) | +| Tunnel fails to connect or reports an error | [Troubleshooting](https://developers.cloudflare.com/tunnel/troubleshooting/index.md) | +| Tunnel is healthy but an HTTPS application fails or redirects | [HTTPS origins](https://developers.cloudflare.com/tunnel/troubleshooting/https-origins/index.md) | +| Inspect connection health and application diagnostics | [Monitoring](https://developers.cloudflare.com/tunnel/observability/index.md) | +| Check local configuration and rule matching | [Configuration file](https://developers.cloudflare.com/tunnel/features/locally-managed-tunnels/configuration-file/index.md) | +| Connections behave unexpectedly after token rotation | [Tunnel tokens](https://developers.cloudflare.com/tunnel/reference/tunnel-tokens/index.md) | +| Check replica capacity or firewall requirements | [Configuration](https://developers.cloudflare.com/tunnel/configuration/index.md) | +| Update an existing installation | [Update cloudflared](https://developers.cloudflare.com/tunnel/guides/update-cloudflared/index.md) | Separate tunnel health from origin availability. Check the service address, protocol, and certificate before relaxing verification. Scope operational changes to the intended tunnel and replicas; follow the documented rotation and upgrade sequence instead of stopping every cloudflared process on a host. See [networking.md](./networking.md) for connectivity checks. diff --git a/external/develop/devops/cloudflare/references/tunnel/networking.md b/external/develop/devops/cloudflare/references/tunnel/networking.md index 3a53f7a..8dcaa46 100644 --- a/external/develop/devops/cloudflare/references/tunnel/networking.md +++ b/external/develop/devops/cloudflare/references/tunnel/networking.md @@ -4,11 +4,11 @@ Investigate the connection from cloudflared to Cloudflare separately from the co | Task | Documentation | | --- | --- | -| Determine required egress ports and destinations | [Firewall rules](https://developers.cloudflare.com/tunnel/configuration/#firewall-rules) | -| Diagnose DNS, QUIC, or TCP connectivity | [Connection errors](https://developers.cloudflare.com/tunnel/troubleshooting/#connection-errors) | -| Configure transport and runtime options | [Run parameters](https://developers.cloudflare.com/tunnel/advanced/run-parameters/) | -| Check service protocols and client requirements | [Routing](https://developers.cloudflare.com/tunnel/routing/) | -| Set up private-network access with Cloudflare One | [Connect private networks with cloudflared](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/private-net/cloudflared/) | -| Inspect tunnel health, logs, and metrics | [Monitoring](https://developers.cloudflare.com/tunnel/monitoring/) | +| Determine required egress ports and destinations | [Firewall rules](https://developers.cloudflare.com/tunnel/configuration/index.md#firewall-rules) | +| Diagnose DNS, QUIC, or TCP connectivity | [Connection errors](https://developers.cloudflare.com/tunnel/troubleshooting/index.md#connection-errors) | +| Configure transport and runtime options | [Run parameters](https://developers.cloudflare.com/tunnel/reference/run-parameters/index.md) | +| Check service protocols and client requirements | [Routing](https://developers.cloudflare.com/tunnel/concepts/routing/index.md) | +| Set up private-network access with Cloudflare One | [Connect private networks with cloudflared](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/private-net/cloudflared/index.md) | +| Inspect tunnel health, logs, and metrics | [Monitoring](https://developers.cloudflare.com/tunnel/observability/index.md) | Test from the machine or container running cloudflared. Compare the actual firewall policy with the current documented destinations; do not infer transport ports from the origin protocol. For private access, follow the linked Cloudflare One setup for routes and device-client configuration instead of reusing public-hostname instructions. See [gotchas.md](./gotchas.md) for origin failures. diff --git a/external/develop/devops/cloudflare/references/tunnel/patterns.md b/external/develop/devops/cloudflare/references/tunnel/patterns.md index dc3d34b..7344c75 100644 --- a/external/develop/devops/cloudflare/references/tunnel/patterns.md +++ b/external/develop/devops/cloudflare/references/tunnel/patterns.md @@ -4,12 +4,12 @@ Choose deployment ownership and availability requirements before adapting an exa | Task | Documentation | | --- | --- | -| Install a remotely-managed tunnel on a host or in Docker | [Setup](https://developers.cloudflare.com/tunnel/setup/) | -| Deploy cloudflared inside a cluster | [Kubernetes](https://developers.cloudflare.com/tunnel/deployment-guides/kubernetes/) | -| Manage tunnel infrastructure declaratively | [Terraform](https://developers.cloudflare.com/tunnel/deployment-guides/terraform/) | -| Deploy replicas and check current capacity | [Replicas and high availability](https://developers.cloudflare.com/tunnel/configuration/#replicas-and-high-availability) | -| Choose redundancy or explicit traffic steering | [Routing](https://developers.cloudflare.com/tunnel/routing/) | -| Plan upgrades for the existing installation method | [Update cloudflared](https://developers.cloudflare.com/tunnel/downloads/update-cloudflared/) | -| Add authentication or private connectivity | [Integrations](https://developers.cloudflare.com/tunnel/integrations/) | +| Install a remotely-managed tunnel on a host or in Docker | [Setup](https://developers.cloudflare.com/tunnel/get-started/index.md) | +| Deploy cloudflared inside a cluster | [Kubernetes](https://developers.cloudflare.com/tunnel/guides/kubernetes/index.md) | +| Manage tunnel infrastructure declaratively | [Terraform](https://developers.cloudflare.com/tunnel/guides/terraform/index.md) | +| Deploy replicas and check current capacity | [Replicas and high availability](https://developers.cloudflare.com/tunnel/configuration/index.md#replicas-and-high-availability) | +| Choose redundancy or explicit traffic steering | [Routing](https://developers.cloudflare.com/tunnel/concepts/routing/index.md) | +| Plan upgrades for the existing installation method | [Update cloudflared](https://developers.cloudflare.com/tunnel/guides/update-cloudflared/index.md) | +| Add authentication or private connectivity | [Integrations](https://developers.cloudflare.com/tunnel/integrations/index.md) | Establish which service each replica can reach and how configuration and credentials reach each host. Decide whether simple redundancy meets the requirement or whether health-based routing needs a load balancer. Verify replacement replicas before retiring existing ones, and include application-level checks in the rollout. See [api.md](./api.md) for programmatic operations. diff --git a/external/develop/devops/cloudflare/references/turn/README.md b/external/develop/devops/cloudflare/references/turn/README.md deleted file mode 100644 index cc4b39e..0000000 --- a/external/develop/devops/cloudflare/references/turn/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# Cloudflare TURN Service - -Expert guidance for implementing Cloudflare TURN Service in WebRTC applications. - -## Overview - -Cloudflare TURN (Traversal Using Relays around NAT) Service is a managed relay service for WebRTC applications. TURN acts as a relay point for traffic between WebRTC clients and SFUs, particularly when direct peer-to-peer communication is obstructed by NATs or firewalls. The service runs on Cloudflare's global anycast network across 310+ cities. - -## Key Characteristics - -- **Anycast Architecture**: Automatically connects clients to the closest Cloudflare location -- **Global Network**: Available across Cloudflare's entire network (excluding China Network) -- **Zero Configuration**: No need to manually select regions or servers -- **Protocol Support**: STUN/TURN over UDP, TCP, and TLS -- **Free Tier**: Free when used with Cloudflare Calls SFU, otherwise $0.05/GB outbound - -## In This Reference - -| File | Purpose | -|------|---------| -| [api.md](./api.md) | Credentials API, TURN key management, types, constraints | -| [configuration.md](./configuration.md) | Worker setup, wrangler.jsonc, env vars, IP allowlisting | -| [patterns.md](./patterns.md) | Implementation patterns, use cases, integration examples | -| [gotchas.md](./gotchas.md) | Troubleshooting, limits, security, common mistakes | - -## Reading Order - -| Task | Files to Read | Est. Tokens | -|------|---------------|-------------| -| Quick start | README only | ~500 | -| Generate credentials | README → api | ~1300 | -| Worker integration | README → configuration → patterns | ~2000 | -| Debug connection | gotchas | ~700 | -| Security review | api → gotchas | ~1500 | -| Enterprise firewall | configuration | ~600 | - -## Service Addresses and Ports - -### STUN over UDP -- **Primary**: `stun.cloudflare.com:3478/udp` -- **Alternate**: `stun.cloudflare.com:53/udp` (blocked by browsers, not recommended) - -### TURN over UDP -- **Primary**: `turn.cloudflare.com:3478/udp` -- **Alternate**: `turn.cloudflare.com:53/udp` (blocked by browsers) - -### TURN over TCP -- **Primary**: `turn.cloudflare.com:3478/tcp` -- **Alternate**: `turn.cloudflare.com:80/tcp` - -### TURN over TLS -- **Primary**: `turn.cloudflare.com:5349/tcp` -- **Alternate**: `turn.cloudflare.com:443/tcp` - -## Quick Start - -1. **Create TURN key via API**: see [api.md#create-turn-key](./api.md#create-turn-key) -2. **Generate credentials**: see [api.md#generate-temporary-credentials](./api.md#generate-temporary-credentials) -3. **Configure Worker**: see [configuration.md#cloudflare-worker-integration](./configuration.md#cloudflare-worker-integration) -4. **Implement client**: see [patterns.md#basic-turn-configuration-browser](./patterns.md#basic-turn-configuration-browser) - -## When to Use TURN - -- **Restrictive NATs**: Symmetric NATs that block direct connections -- **Corporate firewalls**: Environments blocking WebRTC ports -- **Mobile networks**: Carrier-grade NAT scenarios -- **Predictable connectivity**: When reliability > efficiency - -## Related Cloudflare Services - -- **Cloudflare Calls SFU**: Managed Selective Forwarding Unit (TURN free when used with SFU) -- **Cloudflare Stream**: Video streaming with WHIP/WHEP support -- **Cloudflare Workers**: Backend for credential generation -- **Cloudflare KV**: Credential caching -- **Cloudflare Durable Objects**: Session state management - -## Additional Resources - -- [Cloudflare Calls Documentation](https://developers.cloudflare.com/calls/) -- [Cloudflare TURN Service Docs](https://developers.cloudflare.com/realtime/turn/) -- [Cloudflare API Reference](https://developers.cloudflare.com/api/resources/calls/subresources/turn/) -- [Orange Meets (Open Source Example)](https://github.com/cloudflare/orange) diff --git a/external/develop/devops/cloudflare/references/turn/api.md b/external/develop/devops/cloudflare/references/turn/api.md deleted file mode 100644 index 498f5e4..0000000 --- a/external/develop/devops/cloudflare/references/turn/api.md +++ /dev/null @@ -1,239 +0,0 @@ -# TURN API Reference - -Complete API documentation for Cloudflare TURN service credentials and key management. - -## Authentication - -All endpoints require Cloudflare API token with "Calls Write" permission. - -Base URL: `https://api.cloudflare.com/client/v4` - -## TURN Key Management - -### List TURN Keys - -``` -GET /accounts/{account_id}/calls/turn_keys -``` - -### Get TURN Key Details - -``` -GET /accounts/{account_id}/calls/turn_keys/{key_id} -``` - -### Create TURN Key - -``` -POST /accounts/{account_id}/calls/turn_keys -Content-Type: application/json - -{ - "name": "my-turn-key" -} -``` - -**Response includes**: -- `uid`: Key identifier -- `key`: The actual secret key (only returned on creation—save immediately) -- `name`: Human-readable name -- `created`: ISO 8601 timestamp -- `modified`: ISO 8601 timestamp - -### Update TURN Key - -``` -PUT /accounts/{account_id}/calls/turn_keys/{key_id} -Content-Type: application/json - -{ - "name": "updated-name" -} -``` - -### Delete TURN Key - -``` -DELETE /accounts/{account_id}/calls/turn_keys/{key_id} -``` - -## Generate Temporary Credentials - -``` -POST https://rtc.live.cloudflare.com/v1/turn/keys/{key_id}/credentials/generate -Authorization: Bearer {key_secret} -Content-Type: application/json - -{ - "ttl": 86400 -} -``` - -### Credential Constraints - -| Parameter | Min | Max | Default | Notes | -|-----------|-----|-----|---------|-------| -| ttl | 1 | 172800 (48hrs) | varies | API rejects values >172800 | - -**CRITICAL**: Maximum TTL is 48 hours (172800 seconds). API will reject requests exceeding this limit. - -### Response Schema - -```json -{ - "iceServers": { - "urls": [ - "stun:stun.cloudflare.com:3478", - "turn:turn.cloudflare.com:3478?transport=udp", - "turn:turn.cloudflare.com:3478?transport=tcp", - "turn:turn.cloudflare.com:53?transport=udp", - "turn:turn.cloudflare.com:80?transport=tcp", - "turns:turn.cloudflare.com:5349?transport=tcp", - "turns:turn.cloudflare.com:443?transport=tcp" - ], - "username": "1738035200:user123", - "credential": "base64encodedhmac==" - } -} -``` - -**Port 53 Warning**: Filter port 53 URLs for browser clients—blocked by Chrome/Firefox. See [gotchas.md](./gotchas.md#using-port-53-in-browsers). - -## Revoke Credentials - -``` -POST https://rtc.live.cloudflare.com/v1/turn/keys/{key_id}/credentials/revoke -Authorization: Bearer {key_secret} -Content-Type: application/json - -{ - "username": "1738035200:user123" -} -``` - -**Response**: 204 No Content - -Billing stops immediately. Active connection drops after short delay (~seconds). - -## TypeScript Types - -```typescript -interface CloudflareTURNConfig { - keyId: string; - keySecret: string; - ttl?: number; // Max 172800 (48 hours) -} - -interface TURNCredentialsRequest { - ttl?: number; // Max 172800 seconds -} - -interface TURNCredentialsResponse { - iceServers: { - urls: string[]; - username: string; - credential: string; - }; -} - -interface RTCIceServer { - urls: string | string[]; - username?: string; - credential?: string; - credentialType?: "password"; -} - -interface TURNKeyResponse { - uid: string; - key: string; // Only present on creation - name: string; - created: string; - modified: string; -} -``` - -## Validation Function - -```typescript -function validateRTCIceServer(obj: unknown): obj is RTCIceServer { - if (!obj || typeof obj !== 'object') { - return false; - } - - const server = obj as Record; - - if (typeof server.urls !== 'string' && !Array.isArray(server.urls)) { - return false; - } - - if (server.username && typeof server.username !== 'string') { - return false; - } - - if (server.credential && typeof server.credential !== 'string') { - return false; - } - - return true; -} -``` - -## Type-Safe Credential Generation - -```typescript -async function fetchTURNServers( - config: CloudflareTURNConfig -): Promise { - // Validate TTL constraint - const ttl = config.ttl ?? 3600; - if (ttl > 172800) { - throw new Error('TTL cannot exceed 172800 seconds (48 hours)'); - } - - const response = await fetch( - `https://rtc.live.cloudflare.com/v1/turn/keys/${config.keyId}/credentials/generate`, - { - method: 'POST', - headers: { - 'Authorization': `Bearer ${config.keySecret}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ ttl }) - } - ); - - if (!response.ok) { - throw new Error(`TURN credential generation failed: ${response.status}`); - } - - const data = await response.json(); - - // Filter port 53 for browser clients - const filteredUrls = data.iceServers.urls.filter( - (url: string) => !url.includes(':53') - ); - - const iceServers = [ - { urls: 'stun:stun.cloudflare.com:3478' }, - { - urls: filteredUrls, - username: data.iceServers.username, - credential: data.iceServers.credential, - credentialType: 'password' as const - } - ]; - - // Validate before returning - if (!iceServers.every(validateRTCIceServer)) { - throw new Error('Invalid ICE server configuration received'); - } - - return iceServers; -} -``` - -## See Also - -- [configuration.md](./configuration.md) - Worker setup, environment variables -- [patterns.md](./patterns.md) - Implementation examples using these APIs -- [gotchas.md](./gotchas.md) - Security best practices, common mistakes diff --git a/external/develop/devops/cloudflare/references/turn/configuration.md b/external/develop/devops/cloudflare/references/turn/configuration.md deleted file mode 100644 index 2d49736..0000000 --- a/external/develop/devops/cloudflare/references/turn/configuration.md +++ /dev/null @@ -1,179 +0,0 @@ -# TURN Configuration - -Setup and configuration for Cloudflare TURN service in Workers and applications. - -## Environment Variables - -```bash -# .env -CLOUDFLARE_ACCOUNT_ID=your_account_id -CLOUDFLARE_API_TOKEN=your_api_token -TURN_KEY_ID=your_turn_key_id -TURN_KEY_SECRET=your_turn_key_secret -``` - -Validate with zod: - -```typescript -import { z } from 'zod'; - -const envSchema = z.object({ - CLOUDFLARE_ACCOUNT_ID: z.string().min(1), - CLOUDFLARE_API_TOKEN: z.string().min(1), - TURN_KEY_ID: z.string().min(1), - TURN_KEY_SECRET: z.string().min(1) -}); - -export const config = envSchema.parse(process.env); -``` - -## wrangler.jsonc - -```jsonc -{ - "name": "turn-credentials-api", - "main": "src/index.ts", - "compatibility_date": "2025-01-01", - "vars": { - "TURN_KEY_ID": "your-turn-key-id" // Non-sensitive, can be in vars - }, - "env": { - "production": { - "kv_namespaces": [ - { - "binding": "CREDENTIALS_CACHE", - "id": "your-kv-namespace-id" - } - ] - } - } -} -``` - -**Store secrets separately**: -```bash -wrangler secret put TURN_KEY_SECRET -``` - -## Cloudflare Worker Integration - -### Worker Binding Types - -```typescript -interface Env { - TURN_KEY_ID: string; - TURN_KEY_SECRET: string; - CREDENTIALS_CACHE?: KVNamespace; -} - -export default { - async fetch(request: Request, env: Env): Promise { - // See patterns.md for implementation - } -} -``` - -### Basic Worker Example - -```typescript -export default { - async fetch(request: Request, env: Env): Promise { - if (request.url.endsWith('/turn-credentials')) { - // Validate client auth - const authHeader = request.headers.get('Authorization'); - if (!authHeader) { - return new Response('Unauthorized', { status: 401 }); - } - - const response = await fetch( - `https://rtc.live.cloudflare.com/v1/turn/keys/${env.TURN_KEY_ID}/credentials/generate`, - { - method: 'POST', - headers: { - 'Authorization': `Bearer ${env.TURN_KEY_SECRET}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ ttl: 3600 }) - } - ); - - if (!response.ok) { - return new Response('Failed to generate credentials', { status: 500 }); - } - - const data = await response.json(); - - // Filter port 53 for browser clients - const filteredUrls = data.iceServers.urls.filter( - (url: string) => !url.includes(':53') - ); - - return Response.json({ - iceServers: [ - { urls: 'stun:stun.cloudflare.com:3478' }, - { - urls: filteredUrls, - username: data.iceServers.username, - credential: data.iceServers.credential - } - ] - }); - } - - return new Response('Not found', { status: 404 }); - } -}; -``` - -## IP Allowlisting (Enterprise/Firewall) - -For strict firewalls, allowlist these IPs for `turn.cloudflare.com`: - -| Type | Address | Protocol | -|------|---------|----------| -| IPv4 | 141.101.90.1/32 | All | -| IPv4 | 162.159.207.1/32 | All | -| IPv6 | 2a06:98c1:3200::1/128 | All | -| IPv6 | 2606:4700:48::1/128 | All | - -**IMPORTANT**: These IPs may change with 14-day notice. Monitor DNS: - -```bash -# Check A and AAAA records -dig turn.cloudflare.com A -dig turn.cloudflare.com AAAA -``` - -Set up automated monitoring to detect IP changes and update allowlists within 14 days. - -## IPv6 Support - -- **Client-to-TURN**: Both IPv4 and IPv6 supported -- **Relay addresses**: IPv4 only (no RFC 6156 support) -- **TCP relaying**: Not supported (RFC 6062) - -Clients can connect via IPv6, but relayed traffic uses IPv4 addresses. - -## TLS Configuration - -### Supported TLS Versions -- TLS 1.1 -- TLS 1.2 -- TLS 1.3 - -### Recommended Ciphers (TLS 1.3) -- AEAD-AES128-GCM-SHA256 -- AEAD-AES256-GCM-SHA384 -- AEAD-CHACHA20-POLY1305-SHA256 - -### Recommended Ciphers (TLS 1.2) -- ECDHE-ECDSA-AES128-GCM-SHA256 -- ECDHE-RSA-AES128-GCM-SHA256 -- ECDHE-RSA-AES128-SHA (also TLS 1.1) -- AES128-GCM-SHA256 - -## See Also - -- [api.md](./api.md) - TURN key creation, credential generation API -- [patterns.md](./patterns.md) - Full Worker implementation patterns -- [gotchas.md](./gotchas.md) - Security best practices, troubleshooting diff --git a/external/develop/devops/cloudflare/references/turn/gotchas.md b/external/develop/devops/cloudflare/references/turn/gotchas.md deleted file mode 100644 index e2d5bd1..0000000 --- a/external/develop/devops/cloudflare/references/turn/gotchas.md +++ /dev/null @@ -1,231 +0,0 @@ -# TURN Gotchas & Troubleshooting - -Common mistakes, security best practices, and troubleshooting for Cloudflare TURN. - -## Quick Reference - -| Issue | Solution | Details | -|-------|----------|---------| -| Credentials not working | Check TTL ≤ 48hrs | [See Troubleshooting](#issue-turn-credentials-not-working) | -| Connection drops after ~48hrs | Implement credential refresh | [See Connection Drops](#issue-connection-drops-after-48-hours) | -| Port 53 fails in browser | Filter server-side | [See Port 53](#using-port-53-in-browsers) | -| High packet loss | Check rate limits | [See Rate Limits](#limits-per-turn-allocation) | -| Connection fails after maintenance | Implement ICE restart | [See ICE Restart](#ice-restart-required-scenarios) | - -## Critical Constraints - -| Constraint | Value | Consequence if Violated | -|------------|-------|-------------------------| -| Max credential TTL | 48 hours (172800s) | API rejects request | -| Credential revocation delay | ~seconds | Billing stops immediately, connection drops shortly | -| IP allowlist update window | 14 days (if IPs change) | Connection fails if IPs change | -| Packet rate | 5-10k pps per allocation | Packet drops | -| Data rate | 50-100 Mbps per allocation | Packet drops | -| Unique IP rate | >5 new IPs/sec | Packet drops | - -## Limits Per TURN Allocation - -**Per user** (not account-wide): - -- **IP addresses**: >5 new unique IPs per second -- **Packet rate**: 5-10k packets per second (inbound/outbound) -- **Data rate**: 50-100 Mbps (inbound/outbound) -- **MTU**: No specific limit -- **Burst rates**: Higher than documented - -Exceeding limits results in **packet drops**. - -## Common Mistakes - -### Setting TTL > 48 hours - -```typescript -// ❌ BAD: API will reject -const creds = await generate({ ttl: 604800 }); // 7 days - -// ✅ GOOD: -const creds = await generate({ ttl: 86400 }); // 24 hours -``` - -### Hardcoding IPs without monitoring - -```typescript -// ❌ BAD: IPs can change with 14-day notice -const iceServers = [{ urls: 'turn:141.101.90.1:3478' }]; - -// ✅ GOOD: Use DNS -const iceServers = [{ urls: 'turn:turn.cloudflare.com:3478' }]; -``` - -### Using port 53 in browsers - -```typescript -// ❌ BAD: Blocked by Chrome/Firefox -urls: ['turn:turn.cloudflare.com:53'] - -// ✅ GOOD: Filter port 53 -urls: urls.filter(url => !url.includes(':53')) -``` - -### Not handling credential expiry - -```typescript -// ❌ BAD: Credentials expire but call continues → connection drops -const creds = await fetchCreds(); -const pc = new RTCPeerConnection({ iceServers: creds }); - -// ✅ GOOD: Refresh before expiry -setInterval(() => refreshCredentials(pc), 3000000); // 50 min -``` - -### Missing ICE restart support - -```typescript -// ❌ BAD: No recovery from TURN maintenance -pc.addEventListener('iceconnectionstatechange', () => { - console.log('State changed:', pc.iceConnectionState); -}); - -// ✅ GOOD: Implement ICE restart -pc.addEventListener('iceconnectionstatechange', async () => { - if (pc.iceConnectionState === 'failed') { - await refreshCredentials(pc); - pc.restartIce(); - } -}); -``` - -### Exposing TURN key secret client-side - -```typescript -// ❌ BAD: Secret exposed to client -const secret = 'your-turn-key-secret'; -const response = await fetch(`https://rtc.live.cloudflare.com/v1/turn/...`, { - headers: { 'Authorization': `Bearer ${secret}` } -}); - -// ✅ GOOD: Generate credentials server-side -const response = await fetch('/api/turn-credentials'); -``` - -## ICE Restart Required Scenarios - -These events require ICE restart (see [patterns.md](./patterns.md#ice-restart-pattern)): - -1. **TURN server maintenance** (occasional on Cloudflare's network) -2. **Network topology changes** (anycast routing changes) -3. **Credential refresh** during long sessions (>1 hour) -4. **Connection failure** (iceConnectionState === 'failed') - -Implement in all production apps: - -```typescript -pc.addEventListener('iceconnectionstatechange', async () => { - if (pc.iceConnectionState === 'failed' || - pc.iceConnectionState === 'disconnected') { - await refreshTURNCredentials(pc); - pc.restartIce(); - const offer = await pc.createOffer({ iceRestart: true }); - await pc.setLocalDescription(offer); - // Send offer to peer via signaling... - } -}); -``` - -Reference: [RFC 8445 Section 2.4](https://datatracker.ietf.org/doc/html/rfc8445#section-2.4) - -## Security Checklist - -- [ ] Credentials generated server-side only (never client-side) -- [ ] TURN_KEY_SECRET in wrangler secrets, not vars -- [ ] TTL ≤ expected session duration (and ≤ 48 hours) -- [ ] Rate limiting on credential generation endpoint -- [ ] Client authentication before issuing credentials -- [ ] Credential revocation API for compromised sessions -- [ ] No hardcoded IPs (or DNS monitoring in place) -- [ ] Port 53 filtered for browser clients - -## Troubleshooting - -### Issue: TURN credentials not working - -**Check:** -- Key ID and secret are correct -- Credentials haven't expired (check TTL) -- TTL doesn't exceed 172800 seconds (48 hours) -- Server can reach rtc.live.cloudflare.com -- Network allows outbound HTTPS - -**Solution:** -```typescript -// Validate before using -if (ttl > 172800) { - throw new Error('TTL cannot exceed 48 hours'); -} -``` - -### Issue: Slow connection establishment - -**Solutions:** -- Ensure proper ICE candidate gathering -- Check network latency to Cloudflare edge -- Verify firewall allows WebRTC ports (3478, 5349, 443) -- Consider using TURN over TLS (port 443) for corporate networks - -### Issue: High packet loss - -**Check:** -- Not exceeding rate limits (5-10k pps) -- Not exceeding bandwidth limits (50-100 Mbps) -- Not connecting to too many unique IPs (>5/sec) -- Client network quality - -### Issue: Connection drops after ~48 hours - -**Cause**: Credentials expired (48hr max) - -**Solution**: -- Set TTL to expected session duration -- Implement credential refresh with setConfiguration() -- Use ICE restart if connection fails - -```typescript -// Refresh credentials before expiry -const refreshInterval = ttl * 1000 - 60000; // 1 min early -setInterval(async () => { - await refreshTURNCredentials(pc); -}, refreshInterval); -``` - -### Issue: Port 53 URLs in browser fail silently - -**Cause**: Chrome/Firefox block port 53 - -**Solution**: Filter port 53 URLs server-side: - -```typescript -const filtered = urls.filter(url => !url.includes(':53')); -``` - -### Issue: Hardcoded IPs stop working - -**Cause**: Cloudflare changed IP addresses (14-day notice) - -**Solution**: -- Use DNS hostnames (`turn.cloudflare.com`) -- Monitor DNS changes with automated alerts -- Update allowlists within 14 days if using IP allowlisting - -## Cost Optimization - -1. Use appropriate TTLs (don't over-provision) -2. Implement credential caching -3. Set `iceTransportPolicy: 'all'` to try direct first (use `'relay'` only when necessary) -4. Monitor bandwidth usage -5. Free when used with Cloudflare Calls SFU - -## See Also - -- [api.md](./api.md) - Credential generation API, revocation -- [configuration.md](./configuration.md) - IP allowlisting, monitoring -- [patterns.md](./patterns.md) - ICE restart, credential refresh patterns diff --git a/external/develop/devops/cloudflare/references/turn/patterns.md b/external/develop/devops/cloudflare/references/turn/patterns.md deleted file mode 100644 index 39333be..0000000 --- a/external/develop/devops/cloudflare/references/turn/patterns.md +++ /dev/null @@ -1,213 +0,0 @@ -# TURN Implementation Patterns - -Production-ready patterns for implementing Cloudflare TURN in WebRTC applications. - -## Prerequisites - -Before implementing these patterns, ensure you have: -- TURN key created: see [api.md#create-turn-key](./api.md#create-turn-key) -- Worker configured: see [configuration.md#cloudflare-worker-integration](./configuration.md#cloudflare-worker-integration) - -## Basic TURN Configuration (Browser) - -```typescript -interface RTCIceServer { - urls: string | string[]; - username?: string; - credential?: string; - credentialType?: "password" | "oauth"; -} - -async function getTURNConfig(): Promise { - const response = await fetch('/api/turn-credentials'); - const data = await response.json(); - - return [ - { - urls: 'stun:stun.cloudflare.com:3478' - }, - { - urls: [ - 'turn:turn.cloudflare.com:3478?transport=udp', - 'turn:turn.cloudflare.com:3478?transport=tcp', - 'turns:turn.cloudflare.com:5349?transport=tcp', - 'turns:turn.cloudflare.com:443?transport=tcp' - ], - username: data.username, - credential: data.credential, - credentialType: 'password' - } - ]; -} - -// Use in RTCPeerConnection -const iceServers = await getTURNConfig(); -const peerConnection = new RTCPeerConnection({ iceServers }); -``` - -## Port Selection Strategy - -Recommended order for browser clients: - -1. **3478/udp** (primary, lowest latency) -2. **3478/tcp** (fallback for UDP-blocked networks) -3. **5349/tls** (corporate firewalls, most reliable) -4. **443/tls** (alternate TLS port, firewall-friendly) - -**Avoid port 53**—blocked by Chrome and Firefox. - -```typescript -function filterICEServersForBrowser(urls: string[]): string[] { - return urls - .filter(url => !url.includes(':53')) // Remove port 53 - .sort((a, b) => { - // Prioritize UDP over TCP over TLS - if (a.includes('transport=udp')) return -1; - if (b.includes('transport=udp')) return 1; - if (a.includes('transport=tcp') && !a.startsWith('turns:')) return -1; - if (b.includes('transport=tcp') && !b.startsWith('turns:')) return 1; - return 0; - }); -} -``` - -## Credential Refresh (Mid-Session) - -When credentials expire during long calls: - -```typescript -async function refreshTURNCredentials(pc: RTCPeerConnection): Promise { - const newCreds = await fetch('/turn-credentials').then(r => r.json()); - const config = pc.getConfiguration(); - config.iceServers = newCreds.iceServers; - pc.setConfiguration(config); - // Note: setConfiguration() does NOT trigger ICE restart - // Combine with restartIce() if connection fails -} - -// Auto-refresh before expiry -setInterval(async () => { - await refreshTURNCredentials(peerConnection); -}, 3000000); // 50 minutes if TTL is 1 hour -``` - -## ICE Restart Pattern - -After network change, TURN server maintenance, or credential expiry: - -```typescript -pc.addEventListener('iceconnectionstatechange', async () => { - if (pc.iceConnectionState === 'failed') { - console.warn('ICE connection failed, restarting...'); - - // Refresh credentials - await refreshTURNCredentials(pc); - - // Trigger ICE restart - pc.restartIce(); - const offer = await pc.createOffer({ iceRestart: true }); - await pc.setLocalDescription(offer); - - // Send offer to peer via signaling channel... - } -}); -``` - -## Credentials Caching Pattern - -```typescript -class TURNCredentialsManager { - private creds: { username: string; credential: string; urls: string[]; expiresAt: number; } | null = null; - - async getCredentials(keyId: string, keySecret: string): Promise { - const now = Date.now(); - - if (this.creds && this.creds.expiresAt > now) { - return this.buildIceServers(this.creds); - } - - const ttl = 3600; - if (ttl > 172800) throw new Error('TTL max 48hrs'); - - const res = await fetch( - `https://rtc.live.cloudflare.com/v1/turn/keys/${keyId}/credentials/generate`, - { - method: 'POST', - headers: { 'Authorization': `Bearer ${keySecret}`, 'Content-Type': 'application/json' }, - body: JSON.stringify({ ttl }) - } - ); - - const data = await res.json(); - const filteredUrls = data.iceServers.urls.filter((url: string) => !url.includes(':53')); - - this.creds = { - username: data.iceServers.username, - credential: data.iceServers.credential, - urls: filteredUrls, - expiresAt: now + (ttl * 1000) - 60000 - }; - - return this.buildIceServers(this.creds); - } - - private buildIceServers(c: { username: string; credential: string; urls: string[] }): RTCIceServer[] { - return [ - { urls: 'stun:stun.cloudflare.com:3478' }, - { urls: c.urls, username: c.username, credential: c.credential, credentialType: 'password' as const } - ]; - } -} -``` - -## Common Use Cases - -```typescript -// Video conferencing: TURN as fallback -const config = { iceServers: await getTURNConfig(), iceTransportPolicy: 'all' }; - -// IoT/predictable connectivity: force TURN -const config = { iceServers: await getTURNConfig(), iceTransportPolicy: 'relay' }; - -// Screen sharing: reduce overhead -const pc = new RTCPeerConnection({ iceServers: await getTURNConfig(), bundlePolicy: 'max-bundle' }); -``` - -## Integration with Cloudflare Calls SFU - -```typescript -// TURN is automatically used when needed -// Cloudflare Calls handles TURN + SFU coordination -const session = await callsClient.createSession({ - appId: 'your-app-id', - sessionId: 'meeting-123' -}); -``` - -## Debugging ICE Connectivity - -```typescript -pc.addEventListener('icecandidate', (event) => { - if (event.candidate) { - console.log('ICE candidate:', event.candidate.type, event.candidate.protocol); - } -}); - -pc.addEventListener('iceconnectionstatechange', () => { - console.log('ICE state:', pc.iceConnectionState); -}); - -// Check selected candidate pair -const stats = await pc.getStats(); -stats.forEach(report => { - if (report.type === 'candidate-pair' && report.selected) { - console.log('Selected:', report); - } -}); -``` - -## See Also - -- [api.md](./api.md) - Credential generation API, types -- [configuration.md](./configuration.md) - Worker setup, environment variables -- [gotchas.md](./gotchas.md) - Common mistakes, troubleshooting diff --git a/external/develop/devops/cloudflare/references/vectorize/README.md b/external/develop/devops/cloudflare/references/vectorize/README.md index 7d01f31..9ad241b 100644 --- a/external/develop/devops/cloudflare/references/vectorize/README.md +++ b/external/develop/devops/cloudflare/references/vectorize/README.md @@ -8,7 +8,7 @@ Fetch current documentation before implementing. Start with the [Vectorize docum | Task | Read | |------|------| -| Create an index and connect a Worker | [Configuration](configuration.md) and [Introduction to Vectorize](https://developers.cloudflare.com/vectorize/get-started/intro/) | +| Create an index and connect a Worker | [Configuration](configuration.md) and [Introduction to Vectorize](https://developers.cloudflare.com/vectorize/get-started/intro/index.md) | | Insert, update, query, retrieve, or delete vectors | [API routes](api.md) | | Generate embeddings, build RAG, or partition tenant data | [Patterns](patterns.md) | | Diagnose missing matches, metadata, or rejected requests | [Gotchas](gotchas.md) | @@ -16,7 +16,7 @@ Fetch current documentation before implementing. Start with the [Vectorize docum ## Decisions to make first - Use a consistent embedding model and preprocessing for stored vectors and queries. Matching dimensions alone does not make different models' embeddings compatible. -- Choose dimensions from the embedding output and a distance metric appropriate to that model. Changing either requires a new index; check [index configuration and scoring semantics](https://developers.cloudflare.com/vectorize/best-practices/create-indexes/) before choosing thresholds. -- Plan filterable metadata before ingestion. Adding an index later requires re-upserting existing vectors to index that metadata; see [metadata filtering](https://developers.cloudflare.com/vectorize/reference/metadata-filtering/). +- Choose dimensions from the embedding output and a distance metric appropriate to that model. Changing either requires a new index; check [index configuration and scoring semantics](https://developers.cloudflare.com/vectorize/best-practices/create-indexes/index.md) before choosing thresholds. +- Plan filterable metadata before ingestion. Adding an index later requires re-upserting existing vectors to index that metadata; see [metadata filtering](https://developers.cloudflare.com/vectorize/reference/metadata-filtering/index.md). - A namespace partitions search; your application must authorize access and derive tenant scope from trusted identity. See [tenant patterns](patterns.md). -- Design for asynchronous mutation visibility rather than assuming a completed write is already searchable. See [mutation semantics](https://developers.cloudflare.com/vectorize/reference/client-api/). +- Design for asynchronous mutation visibility rather than assuming a completed write is already searchable. See [mutation semantics](https://developers.cloudflare.com/vectorize/reference/client-api/index.md). diff --git a/external/develop/devops/cloudflare/references/vectorize/api.md b/external/develop/devops/cloudflare/references/vectorize/api.md index 1922395..358a45d 100644 --- a/external/develop/devops/cloudflare/references/vectorize/api.md +++ b/external/develop/devops/cloudflare/references/vectorize/api.md @@ -1,15 +1,15 @@ # Vectorize API routes -Fetch the relevant section of the [Workers binding API](https://developers.cloudflare.com/vectorize/reference/client-api/) before writing calls or types. +Fetch the relevant section of the [Workers binding API](https://developers.cloudflare.com/vectorize/reference/client-api/index.md) before writing calls or types. | Task | Current documentation | |------|-----------------------| -| Vector shape, binding, and generated TypeScript types | [Vectorize API](https://developers.cloudflare.com/vectorize/reference/client-api/) | -| Insert, upsert, retrieve by ID, delete, or inspect an index | [Operations](https://developers.cloudflare.com/vectorize/reference/client-api/#operations) | -| Query by vector or ID; choose returned metadata, values, and scoring precision | [Query vectors](https://developers.cloudflare.com/vectorize/best-practices/query-vectors/) and [query options](https://developers.cloudflare.com/vectorize/reference/client-api/#query-vectors) | -| Filter by metadata, combine conditions, or use nested properties | [Metadata filtering](https://developers.cloudflare.com/vectorize/reference/metadata-filtering/) | -| Batch ingestion and select vector formats | [Insert vectors](https://developers.cloudflare.com/vectorize/best-practices/insert-vectors/) and [current limits](https://developers.cloudflare.com/vectorize/platform/limits/) | -| Manage indexes or vectors outside a Worker | [Wrangler commands](https://developers.cloudflare.com/vectorize/reference/wrangler-commands/) and [REST API](https://developers.cloudflare.com/api/resources/vectorize/subresources/indexes/methods/list/) | +| Vector shape, binding, and generated TypeScript types | [Vectorize API](https://developers.cloudflare.com/vectorize/reference/client-api/index.md) | +| Insert, upsert, retrieve by ID, delete, or inspect an index | [Operations](https://developers.cloudflare.com/vectorize/reference/client-api/index.md#operations) | +| Query by vector or ID; choose returned metadata, values, and scoring precision | [Query vectors](https://developers.cloudflare.com/vectorize/best-practices/query-vectors/index.md) and [query options](https://developers.cloudflare.com/vectorize/reference/client-api/index.md#query-vectors) | +| Filter by metadata, combine conditions, or use nested properties | [Metadata filtering](https://developers.cloudflare.com/vectorize/reference/metadata-filtering/index.md) | +| Batch ingestion and select vector formats | [Insert vectors](https://developers.cloudflare.com/vectorize/best-practices/insert-vectors/index.md) and [current limits](https://developers.cloudflare.com/vectorize/platform/limits/index.md) | +| Manage indexes or vectors outside a Worker | [Wrangler commands](https://developers.cloudflare.com/vectorize/reference/wrangler-commands/index.md) and [REST API](https://developers.cloudflare.com/api/resources/vectorize/subresources/indexes/methods/list/index.md) | ## Operation choices diff --git a/external/develop/devops/cloudflare/references/vectorize/configuration.md b/external/develop/devops/cloudflare/references/vectorize/configuration.md index 42d4f28..6e083ac 100644 --- a/external/develop/devops/cloudflare/references/vectorize/configuration.md +++ b/external/develop/devops/cloudflare/references/vectorize/configuration.md @@ -2,13 +2,13 @@ | Task | Current documentation | |------|-----------------------| -| Create an index, choose dimensions and metric | [Create indexes](https://developers.cloudflare.com/vectorize/best-practices/create-indexes/) | -| Bind an index to a Worker, develop, deploy, and verify queries | [Introduction to Vectorize](https://developers.cloudflare.com/vectorize/get-started/intro/) | -| Configure bindings and generate types | [Binding and TypeScript guidance](https://developers.cloudflare.com/vectorize/reference/client-api/#binding-to-a-worker) | -| Create, list, or delete metadata indexes | [Metadata filtering](https://developers.cloudflare.com/vectorize/reference/metadata-filtering/) and [Wrangler commands](https://developers.cloudflare.com/vectorize/reference/wrangler-commands/) | -| Manage indexes and vectors through the CLI | [Wrangler commands](https://developers.cloudflare.com/vectorize/reference/wrangler-commands/) | -| Upload NDJSON and batch ingestion | [Insert vectors](https://developers.cloudflare.com/vectorize/best-practices/insert-vectors/) | -| Check capacity, payload, namespace, or batch constraints | [Limits](https://developers.cloudflare.com/vectorize/platform/limits/) | +| Create an index, choose dimensions and metric | [Create indexes](https://developers.cloudflare.com/vectorize/best-practices/create-indexes/index.md) | +| Bind an index to a Worker, develop, deploy, and verify queries | [Introduction to Vectorize](https://developers.cloudflare.com/vectorize/get-started/intro/index.md) | +| Configure bindings and generate types | [Binding and TypeScript guidance](https://developers.cloudflare.com/vectorize/reference/client-api/index.md#binding-to-a-worker) | +| Create, list, or delete metadata indexes | [Metadata filtering](https://developers.cloudflare.com/vectorize/reference/metadata-filtering/index.md) and [Wrangler commands](https://developers.cloudflare.com/vectorize/reference/wrangler-commands/index.md) | +| Manage indexes and vectors through the CLI | [Wrangler commands](https://developers.cloudflare.com/vectorize/reference/wrangler-commands/index.md) | +| Upload NDJSON and batch ingestion | [Insert vectors](https://developers.cloudflare.com/vectorize/best-practices/insert-vectors/index.md) | +| Check capacity, payload, namespace, or batch constraints | [Limits](https://developers.cloudflare.com/vectorize/platform/limits/index.md) | ## Configuration decisions @@ -16,4 +16,4 @@ Confirm the embedding model, output dimensions, and distance metric before provi Create metadata indexes before ingesting vectors that must be filterable. If adding one to an existing dataset, plan to re-upsert the affected vectors after index creation. -Choose metadata granularity around actual queries. For range filters over high-cardinality fields, consider buckets that preserve the application's required precision; do not bucket identifiers used for exact matches. Fetch the [cardinality guidance](https://developers.cloudflare.com/vectorize/best-practices/insert-vectors/#performance-tips-when-filtering-by-metadata) before designing the schema. +Choose metadata granularity around actual queries. For range filters over high-cardinality fields, consider buckets that preserve the application's required precision; do not bucket identifiers used for exact matches. Fetch the [cardinality guidance](https://developers.cloudflare.com/vectorize/best-practices/insert-vectors/index.md#performance-tips-when-filtering-by-metadata) before designing the schema. diff --git a/external/develop/devops/cloudflare/references/vectorize/gotchas.md b/external/develop/devops/cloudflare/references/vectorize/gotchas.md index c719a9b..ff48b17 100644 --- a/external/develop/devops/cloudflare/references/vectorize/gotchas.md +++ b/external/develop/devops/cloudflare/references/vectorize/gotchas.md @@ -4,12 +4,12 @@ Fetch current documentation before diagnosing a numeric limit, API error, or del | Symptom or decision | What to check | Current documentation | |---------------------|---------------|-----------------------| -| A write succeeded but search has not changed | Mutations are asynchronous; acceptance does not guarantee query visibility | [Insert, upsert, and delete semantics](https://developers.cloudflare.com/vectorize/reference/client-api/#operations) | -| Ingestion is slow or a batch is rejected | Batch size depends on the interface; inspect throughput and payload constraints | [Write throughput](https://developers.cloudflare.com/vectorize/best-practices/insert-vectors/#improve-write-throughput) and [limits](https://developers.cloudflare.com/vectorize/platform/limits/) | -| Query count is rejected or metadata is incomplete | Returned values and metadata affect query limits; indexed metadata can be truncated | [Query options](https://developers.cloudflare.com/vectorize/reference/client-api/#query-vectors) | -| Metadata filters return no matches | Confirm field type, operators, nesting, and index creation; re-upsert data written before the metadata index existed | [Metadata filtering](https://developers.cloudflare.com/vectorize/reference/metadata-filtering/) | -| Query has no matches or poor relevance | Check embedding model and dimensions, metric, namespace, filters, and mutation visibility | [Query vectors](https://developers.cloudflare.com/vectorize/best-practices/query-vectors/) and [index configuration](https://developers.cloudflare.com/vectorize/best-practices/create-indexes/) | -| Existing IDs or metadata behave unexpectedly on update | Insert preserves existing IDs; upsert replaces the full vector and metadata | [Mutation semantics](https://developers.cloudflare.com/vectorize/reference/client-api/#operations) | -| Capacity or model output no longer fits | Check current limits and model output dimensions; changing dimensions or metric requires another index | [Limits](https://developers.cloudflare.com/vectorize/platform/limits/) and [create indexes](https://developers.cloudflare.com/vectorize/best-practices/create-indexes/) | +| A write succeeded but search has not changed | Mutations are asynchronous; acceptance does not guarantee query visibility | [Insert, upsert, and delete semantics](https://developers.cloudflare.com/vectorize/reference/client-api/index.md#operations) | +| Ingestion is slow or a batch is rejected | Batch size depends on the interface; inspect throughput and payload constraints | [Write throughput](https://developers.cloudflare.com/vectorize/best-practices/insert-vectors/index.md#improve-write-throughput) and [limits](https://developers.cloudflare.com/vectorize/platform/limits/index.md) | +| Query count is rejected or metadata is incomplete | Returned values and metadata affect query limits; indexed metadata can be truncated | [Query options](https://developers.cloudflare.com/vectorize/reference/client-api/index.md#query-vectors) | +| Metadata filters return no matches | Confirm field type, operators, nesting, and index creation; re-upsert data written before the metadata index existed | [Metadata filtering](https://developers.cloudflare.com/vectorize/reference/metadata-filtering/index.md) | +| Query has no matches or poor relevance | Check embedding model and dimensions, metric, namespace, filters, and mutation visibility | [Query vectors](https://developers.cloudflare.com/vectorize/best-practices/query-vectors/index.md) and [index configuration](https://developers.cloudflare.com/vectorize/best-practices/create-indexes/index.md) | +| Existing IDs or metadata behave unexpectedly on update | Insert preserves existing IDs; upsert replaces the full vector and metadata | [Mutation semantics](https://developers.cloudflare.com/vectorize/reference/client-api/index.md#operations) | +| Capacity or model output no longer fits | Check current limits and model output dimensions; changing dimensions or metric requires another index | [Limits](https://developers.cloudflare.com/vectorize/platform/limits/index.md) and [create indexes](https://developers.cloudflare.com/vectorize/best-practices/create-indexes/index.md) | For changes to embedding providers or tenant boundaries, also read [pattern decisions](patterns.md). diff --git a/external/develop/devops/cloudflare/references/vectorize/patterns.md b/external/develop/devops/cloudflare/references/vectorize/patterns.md index 414fd1a..531268e 100644 --- a/external/develop/devops/cloudflare/references/vectorize/patterns.md +++ b/external/develop/devops/cloudflare/references/vectorize/patterns.md @@ -2,14 +2,14 @@ | Task | Current documentation | |------|-----------------------| -| Generate and query Workers AI embeddings | [Vectorize and Workers AI](https://developers.cloudflare.com/vectorize/get-started/embeddings/) | -| Query with embeddings from OpenAI | [OpenAI integration](https://developers.cloudflare.com/vectorize/best-practices/query-vectors/#openai) | -| Choose embedding dimensions and distance metric | [Create indexes](https://developers.cloudflare.com/vectorize/best-practices/create-indexes/) | -| Build a retrieval-augmented generation application | [Workers AI RAG tutorial](https://developers.cloudflare.com/workers-ai/guides/tutorials/build-a-retrieval-augmented-generation-ai/) | -| Link search results to source documents | [Vector metadata](https://developers.cloudflare.com/vectorize/best-practices/insert-vectors/#metadata) | -| Partition vectors by tenant | [Namespaces](https://developers.cloudflare.com/vectorize/best-practices/insert-vectors/#namespaces) and [namespace versus metadata filtering](https://developers.cloudflare.com/vectorize/reference/metadata-filtering/#namespace-versus-metadata-filtering) | -| Combine similarity search with categorical or range filters | [Metadata filtering](https://developers.cloudflare.com/vectorize/reference/metadata-filtering/) | -| Ingest or update vectors in batches | [Insert vectors](https://developers.cloudflare.com/vectorize/best-practices/insert-vectors/) and [limits](https://developers.cloudflare.com/vectorize/platform/limits/) | +| Generate and query Workers AI embeddings | [Vectorize and Workers AI](https://developers.cloudflare.com/vectorize/get-started/embeddings/index.md) | +| Query with embeddings from OpenAI | [OpenAI integration](https://developers.cloudflare.com/vectorize/best-practices/query-vectors/index.md#openai) | +| Choose embedding dimensions and distance metric | [Create indexes](https://developers.cloudflare.com/vectorize/best-practices/create-indexes/index.md) | +| Build a retrieval-augmented generation application | [Workers AI RAG tutorial](https://developers.cloudflare.com/workers-ai/guides/tutorials/build-a-retrieval-augmented-generation-ai/index.md) | +| Link search results to source documents | [Vector metadata](https://developers.cloudflare.com/vectorize/best-practices/insert-vectors/index.md#metadata) | +| Partition vectors by tenant | [Namespaces](https://developers.cloudflare.com/vectorize/best-practices/insert-vectors/index.md#namespaces) and [namespace versus metadata filtering](https://developers.cloudflare.com/vectorize/reference/metadata-filtering/index.md#namespace-versus-metadata-filtering) | +| Combine similarity search with categorical or range filters | [Metadata filtering](https://developers.cloudflare.com/vectorize/reference/metadata-filtering/index.md) | +| Ingest or update vectors in batches | [Insert vectors](https://developers.cloudflare.com/vectorize/best-practices/insert-vectors/index.md) and [limits](https://developers.cloudflare.com/vectorize/platform/limits/index.md) | ## Embedding and retrieval decisions diff --git a/external/develop/devops/cloudflare/references/waf/README.md b/external/develop/devops/cloudflare/references/waf/README.md index 2ce866e..5cfb2e8 100644 --- a/external/develop/devops/cloudflare/references/waf/README.md +++ b/external/develop/devops/cloudflare/references/waf/README.md @@ -4,12 +4,12 @@ Use this reference for managed protection, custom request policies, rate limitin | Task | Start here | |------|------------| -| Choose and enable WAF protections | [Get started](https://developers.cloudflare.com/waf/get-started/) | -| Deploy managed protection | [Managed rules deployment](https://developers.cloudflare.com/waf/managed-rules/deploy-api/) | -| Match application-specific requests | [Custom rules](https://developers.cloudflare.com/waf/custom-rules/create-api/) | -| Limit request volume | [Rate limiting](https://developers.cloudflare.com/waf/rate-limiting-rules/create-api/) | -| Understand score-based detection | [Attack score](https://developers.cloudflare.com/waf/detections/attack-score/) | -| Diagnose blocked or unmitigated requests | [Managed rules troubleshooting](https://developers.cloudflare.com/waf/managed-rules/troubleshooting/) | +| Choose and enable WAF protections | [Get started](https://developers.cloudflare.com/waf/get-started/index.md) | +| Deploy managed protection | [Managed rules deployment](https://developers.cloudflare.com/waf/managed-rules/deploy-api/index.md) | +| Match application-specific requests | [Custom rules](https://developers.cloudflare.com/waf/custom-rules/create-api/index.md) | +| Limit request volume | [Rate limiting](https://developers.cloudflare.com/waf/rate-limiting-rules/create-api/index.md) | +| Understand score-based detection | [Attack score](https://developers.cloudflare.com/waf/detections/attack-score/index.md) | +| Diagnose blocked or unmitigated requests | [Managed rules troubleshooting](https://developers.cloudflare.com/waf/managed-rules/troubleshooting/index.md) | Identify the target account or zone and inspect existing rules before planning a change. Keep the requested traffic scope explicit, especially for exceptions and account-wide deployments. diff --git a/external/develop/devops/cloudflare/references/waf/api.md b/external/develop/devops/cloudflare/references/waf/api.md index 850bab0..397c42c 100644 --- a/external/develop/devops/cloudflare/references/waf/api.md +++ b/external/develop/devops/cloudflare/references/waf/api.md @@ -4,14 +4,14 @@ Read the matching workflow before writing API calls or translating them into the | Task | Documentation | |------|---------------| -| Inspect the entry point and add custom rules | [Create a custom rule via API](https://developers.cloudflare.com/waf/custom-rules/create-api/) | -| Discover managed rulesets and deploy them | [Deploy managed rules via API](https://developers.cloudflare.com/waf/managed-rules/deploy-api/) | -| Create rate limits with the current request schema | [Create a rate limiting rule via API](https://developers.cloudflare.com/waf/rate-limiting-rules/create-api/) | -| Select counting characteristics, expressions, periods, and mitigation behavior | [Rate limiting parameters](https://developers.cloudflare.com/waf/rate-limiting-rules/parameters/) | -| Replace a ruleset deliberately or choose an additive operation | [Update or deploy a ruleset](https://developers.cloudflare.com/ruleset-engine/rulesets-api/update/) | -| Construct expressions using supported fields, operators, and functions | [Rules language](https://developers.cloudflare.com/ruleset-engine/rules-language/) | -| Choose actions and understand terminating behavior | [Actions reference](https://developers.cloudflare.com/ruleset-engine/rules-language/actions/) | -| Override managed rules, tags, or a ruleset | [Managed ruleset overrides](https://developers.cloudflare.com/ruleset-engine/managed-rulesets/override-managed-ruleset/) | -| Choose exactly what a skip rule bypasses | [Skip options](https://developers.cloudflare.com/waf/custom-rules/skip/options/) | +| Inspect the entry point and add custom rules | [Create a custom rule via API](https://developers.cloudflare.com/waf/custom-rules/create-api/index.md) | +| Discover managed rulesets and deploy them | [Deploy managed rules via API](https://developers.cloudflare.com/waf/managed-rules/deploy-api/index.md) | +| Create rate limits with the current request schema | [Create a rate limiting rule via API](https://developers.cloudflare.com/waf/rate-limiting-rules/create-api/index.md) | +| Select counting characteristics, expressions, periods, and mitigation behavior | [Rate limiting parameters](https://developers.cloudflare.com/waf/rate-limiting-rules/parameters/index.md) | +| Replace a ruleset deliberately or choose an additive operation | [Update or deploy a ruleset](https://developers.cloudflare.com/ruleset-engine/rulesets-api/update/index.md) | +| Construct expressions using supported fields, operators, and functions | [Rules language](https://developers.cloudflare.com/ruleset-engine/rules-language/index.md) | +| Choose actions and understand terminating behavior | [Actions reference](https://developers.cloudflare.com/ruleset-engine/rules-language/actions/index.md) | +| Override managed rules, tags, or a ruleset | [Managed ruleset overrides](https://developers.cloudflare.com/ruleset-engine/managed-rulesets/override-managed-ruleset/index.md) | +| Choose exactly what a skip rule bypasses | [Skip options](https://developers.cloudflare.com/waf/custom-rules/skip/options/index.md) | For an addition, prefer the workflow's operation that adds a rule to an existing ruleset. When replacing a ruleset, include every rule that must remain; review the resulting rule list before applying it. See [gotchas.md](gotchas.md) for scope and evaluation checks. diff --git a/external/develop/devops/cloudflare/references/waf/configuration.md b/external/develop/devops/cloudflare/references/waf/configuration.md index 23f071b..3b4b46d 100644 --- a/external/develop/devops/cloudflare/references/waf/configuration.md +++ b/external/develop/devops/cloudflare/references/waf/configuration.md @@ -4,14 +4,14 @@ Identify the account or zone, existing rulesets, and the system managing them be | Task | Documentation | |------|---------------| -| Enable protections and configure them in the dashboard | [WAF get started](https://developers.cloudflare.com/waf/get-started/) | -| Configure custom rules through the API or SDK | [Custom rules API workflow](https://developers.cloudflare.com/waf/custom-rules/create-api/) | -| Configure managed rules through the API or SDK | [Managed rules API workflow](https://developers.cloudflare.com/waf/managed-rules/deploy-api/) | -| Configure rate limiting through the API or SDK | [Rate limiting API workflow](https://developers.cloudflare.com/waf/rate-limiting-rules/create-api/) | -| Manage custom rules with Terraform | [Custom rules Terraform guide](https://developers.cloudflare.com/terraform/additional-configurations/waf-custom-rules/) | -| Manage managed rulesets with Terraform | [Managed rules Terraform guide](https://developers.cloudflare.com/terraform/additional-configurations/waf-managed-rulesets/) | -| Manage rate limits with Terraform | [Rate limiting Terraform guide](https://developers.cloudflare.com/terraform/additional-configurations/rate-limiting-rules/) | -| Decide account versus zone placement and phase | [WAF phases](https://developers.cloudflare.com/waf/reference/phases/) | +| Enable protections and configure them in the dashboard | [WAF get started](https://developers.cloudflare.com/waf/get-started/index.md) | +| Configure custom rules through the API or SDK | [Custom rules API workflow](https://developers.cloudflare.com/waf/custom-rules/create-api/index.md) | +| Configure managed rules through the API or SDK | [Managed rules API workflow](https://developers.cloudflare.com/waf/managed-rules/deploy-api/index.md) | +| Configure rate limiting through the API or SDK | [Rate limiting API workflow](https://developers.cloudflare.com/waf/rate-limiting-rules/create-api/index.md) | +| Manage custom rules with Terraform | [Custom rules Terraform guide](https://developers.cloudflare.com/terraform/additional-configurations/waf-custom-rules/index.md) | +| Manage managed rulesets with Terraform | [Managed rules Terraform guide](https://developers.cloudflare.com/terraform/additional-configurations/waf-managed-rulesets/index.md) | +| Manage rate limits with Terraform | [Rate limiting Terraform guide](https://developers.cloudflare.com/terraform/additional-configurations/rate-limiting-rules/index.md) | +| Decide account versus zone placement and phase | [WAF phases](https://developers.cloudflare.com/waf/reference/phases/index.md) | When adopting existing rules into Terraform, follow the selected guide's import instructions and inspect the plan for unintended removals. Keep the existing management tool when it fits the task. For SDK or Pulumi projects, verify the installed package's types before translating the documented API workflow. diff --git a/external/develop/devops/cloudflare/references/waf/gotchas.md b/external/develop/devops/cloudflare/references/waf/gotchas.md index 5a0943f..aaa0d66 100644 --- a/external/develop/devops/cloudflare/references/waf/gotchas.md +++ b/external/develop/devops/cloudflare/references/waf/gotchas.md @@ -4,15 +4,15 @@ Use observed requests and the deployed ruleset definitions to diagnose behavior | Symptom or decision | Documentation to read | |---------------------|-----------------------| -| A rule executes earlier or later than expected | [WAF phases and account/zone order](https://developers.cloudflare.com/waf/reference/phases/) and [terminating actions](https://developers.cloudflare.com/ruleset-engine/rules-language/actions/) | -| A skip rule leaves a protection active | [Skip scope, phases, products, and logging](https://developers.cloudflare.com/waf/custom-rules/skip/options/) | -| Updating a ruleset removes unrelated rules | [Ruleset replacement semantics](https://developers.cloudflare.com/ruleset-engine/rulesets-api/update/) | -| An expression fails to parse | [Rules language elements](https://developers.cloudflare.com/ruleset-engine/rules-language/) | -| Score-based rules match unexpected traffic | [Attack score meaning, special values, and plan availability](https://developers.cloudflare.com/waf/detections/attack-score/) | -| Managed overrides conflict | [Override precedence and scope](https://developers.cloudflare.com/ruleset-engine/managed-rulesets/override-managed-ruleset/) | -| Legitimate traffic is blocked, or attacks reach the origin | [False-positive and false-negative investigation](https://developers.cloudflare.com/waf/managed-rules/troubleshooting/) | -| Rate limits affect shared-IP users or count unexpected requests | [Characteristics, NAT support, and counting expressions](https://developers.cloudflare.com/waf/rate-limiting-rules/parameters/) | -| API creation fails or the request body is unclear | [Custom rule creation](https://developers.cloudflare.com/waf/custom-rules/create-api/), [managed deployment](https://developers.cloudflare.com/waf/managed-rules/deploy-api/), or [rate limit creation](https://developers.cloudflare.com/waf/rate-limiting-rules/create-api/) | +| A rule executes earlier or later than expected | [WAF phases and account/zone order](https://developers.cloudflare.com/waf/reference/phases/index.md) and [terminating actions](https://developers.cloudflare.com/ruleset-engine/rules-language/actions/index.md) | +| A skip rule leaves a protection active | [Skip scope, phases, products, and logging](https://developers.cloudflare.com/waf/custom-rules/skip/options/index.md) | +| Updating a ruleset removes unrelated rules | [Ruleset replacement semantics](https://developers.cloudflare.com/ruleset-engine/rulesets-api/update/index.md) | +| An expression fails to parse | [Rules language elements](https://developers.cloudflare.com/ruleset-engine/rules-language/index.md) | +| Score-based rules match unexpected traffic | [Attack score meaning, special values, and plan availability](https://developers.cloudflare.com/waf/detections/attack-score/index.md) | +| Managed overrides conflict | [Override precedence and scope](https://developers.cloudflare.com/ruleset-engine/managed-rulesets/override-managed-ruleset/index.md) | +| Legitimate traffic is blocked, or attacks reach the origin | [False-positive and false-negative investigation](https://developers.cloudflare.com/waf/managed-rules/troubleshooting/index.md) | +| Rate limits affect shared-IP users or count unexpected requests | [Characteristics, NAT support, and counting expressions](https://developers.cloudflare.com/waf/rate-limiting-rules/parameters/index.md) | +| API creation fails or the request body is unclear | [Custom rule creation](https://developers.cloudflare.com/waf/custom-rules/create-api/index.md), [managed deployment](https://developers.cloudflare.com/waf/managed-rules/deploy-api/index.md), or [rate limit creation](https://developers.cloudflare.com/waf/rate-limiting-rules/create-api/index.md) | Check account versus zone scope, rule position, and the action that actually handled the request. For rate limiting, inspect matching and counting criteria separately. Avoid broad exceptions as a shortcut for diagnosing a single false positive. diff --git a/external/develop/devops/cloudflare/references/waf/patterns.md b/external/develop/devops/cloudflare/references/waf/patterns.md index 00eebf8..a035832 100644 --- a/external/develop/devops/cloudflare/references/waf/patterns.md +++ b/external/develop/devops/cloudflare/references/waf/patterns.md @@ -4,14 +4,14 @@ Choose the workflow that matches the requested outcome, then retrieve its curren | Outcome | Documentation | |---------|---------------| -| Apply managed protection to a zone | [Deploy managed rules](https://developers.cloudflare.com/waf/managed-rules/deploy-api/) | -| Change a managed rule's behavior or evaluate it with logging | [Override a managed ruleset](https://developers.cloudflare.com/ruleset-engine/managed-rulesets/override-managed-ruleset/) | -| Enforce an application-specific request policy | [Create a custom rule](https://developers.cloudflare.com/waf/custom-rules/create-api/) | -| Use attack detection in a request policy | [Attack score semantics and availability](https://developers.cloudflare.com/waf/detections/attack-score/) | -| Protect a login or API endpoint from excessive requests | [Rate limiting API examples](https://developers.cloudflare.com/waf/rate-limiting-rules/create-api/) and [counting parameters](https://developers.cloudflare.com/waf/rate-limiting-rules/parameters/) | -| Exempt narrowly identified traffic from selected protections | [Available skip options](https://developers.cloudflare.com/waf/custom-rules/skip/options/) | -| Adjust protection after a false positive | [Managed rules troubleshooting](https://developers.cloudflare.com/waf/managed-rules/troubleshooting/) | +| Apply managed protection to a zone | [Deploy managed rules](https://developers.cloudflare.com/waf/managed-rules/deploy-api/index.md) | +| Change a managed rule's behavior or evaluate it with logging | [Override a managed ruleset](https://developers.cloudflare.com/ruleset-engine/managed-rulesets/override-managed-ruleset/index.md) | +| Enforce an application-specific request policy | [Create a custom rule](https://developers.cloudflare.com/waf/custom-rules/create-api/index.md) | +| Use attack detection in a request policy | [Attack score semantics and availability](https://developers.cloudflare.com/waf/detections/attack-score/index.md) | +| Protect a login or API endpoint from excessive requests | [Rate limiting API examples](https://developers.cloudflare.com/waf/rate-limiting-rules/create-api/index.md) and [counting parameters](https://developers.cloudflare.com/waf/rate-limiting-rules/parameters/index.md) | +| Exempt narrowly identified traffic from selected protections | [Available skip options](https://developers.cloudflare.com/waf/custom-rules/skip/options/index.md) | +| Adjust protection after a false positive | [Managed rules troubleshooting](https://developers.cloudflare.com/waf/managed-rules/troubleshooting/index.md) | -For a false positive, identify the matching rule and request scope before choosing an exception or override. Keep the adjustment as narrow as the evidence supports. For combined protections, check [WAF phases](https://developers.cloudflare.com/waf/reference/phases/) before deciding where an exception belongs. +For a false positive, identify the matching rule and request scope before choosing an exception or override. Keep the adjustment as narrow as the evidence supports. For combined protections, check [WAF phases](https://developers.cloudflare.com/waf/reference/phases/index.md) before deciding where an exception belongs. Use [configuration.md](configuration.md) to select the deployment method and [api.md](api.md) to preserve existing rules while changing it. diff --git a/external/develop/devops/cloudflare/references/web-analytics/README.md b/external/develop/devops/cloudflare/references/web-analytics/README.md deleted file mode 100644 index 5441ef1..0000000 --- a/external/develop/devops/cloudflare/references/web-analytics/README.md +++ /dev/null @@ -1,141 +0,0 @@ -# Cloudflare Web Analytics - -Privacy-first web analytics providing Core Web Vitals, traffic metrics, and user insights without compromising visitor privacy. - -## Overview - -Cloudflare Web Analytics provides: -- **Core Web Vitals** - LCP, FID, CLS, INP, TTFB monitoring -- **Page views & visits** - Traffic patterns without cookies -- **Referrers & paths** - Traffic sources and popular pages -- **Device & browser data** - User agent breakdown -- **Geographic data** - Country-level visitor distribution -- **Privacy-first** - No cookies, fingerprinting, or PII collection -- **Free** - No cost, unlimited pageviews - -**Important:** Web Analytics is **dashboard-only**. No API exists for programmatic data access. - -## Quick Start Decision Tree - -``` -Is your site proxied through Cloudflare? -├─ YES → Use automatic injection (configuration.md) -│ ├─ Enable auto-injection in dashboard -│ └─ No code changes needed (unless Cache-Control: no-transform) -│ -└─ NO → Use manual beacon integration (integration.md) - ├─ Add JS snippet to HTML - ├─ Use spa: true for React/Vue/Next.js - └─ Configure CSP if needed -``` - -## Reading Order - -1. **[configuration.md](configuration.md)** - Setup for proxied vs non-proxied sites -2. **[integration.md](integration.md)** - Framework-specific beacon integration (React, Next.js, Vue, Nuxt, etc.) -3. **[patterns.md](patterns.md)** - Common use cases (performance monitoring, GDPR consent, multi-site tracking) -4. **[gotchas.md](gotchas.md)** - Troubleshooting (SPA tracking, CSP issues, hash routing limitations) - -## When to Use Each File - -- **Setting up for first time?** → Start with configuration.md -- **Using React/Next.js/Vue/Nuxt?** → Go to integration.md for framework code -- **Need GDPR consent loading?** → See patterns.md -- **Beacon not loading or no data?** → Check gotchas.md -- **SPA not tracking navigation?** → See integration.md for `spa: true` config - -## Key Concepts - -### Proxied vs Non-Proxied Sites - -| Type | Description | Beacon Injection | Limit | -|------|-------------|------------------|-------| -| **Proxied** | DNS through Cloudflare (orange cloud) | Automatic or manual | Unlimited | -| **Non-proxied** | External hosting, manual beacon | Manual only | 10 sites max | - -### SPA Mode - -**Critical for modern frameworks:** -```json -{"token": "YOUR_TOKEN", "spa": true} -``` - -Without `spa: true`, client-side navigation (React Router, Vue Router, Next.js routing) will NOT be tracked. Only initial page loads will register. - -### CSP Requirements - -If using Content Security Policy, allow both domains: -``` -script-src https://static.cloudflareinsights.com https://cloudflareinsights.com; -``` - -## Features - -### Core Web Vitals Debugging -- **LCP (Largest Contentful Paint)** - Identifies slow-loading hero images/elements -- **FID (First Input Delay)** - Interaction responsiveness (legacy metric) -- **INP (Interaction to Next Paint)** - Modern interaction responsiveness metric -- **CLS (Cumulative Layout Shift)** - Visual stability issues -- **TTFB (Time to First Byte)** - Server response performance - -Dashboard shows top 5 problematic elements with CSS selectors for debugging. - -### Traffic Filters -- **Bot filtering** - Exclude automated traffic from metrics -- **Date ranges** - Custom time period analysis -- **Geographic** - Country-level filtering -- **Device type** - Desktop, mobile, tablet breakdown -- **Browser/OS** - User agent filtering - -### Rules (Advanced - Plan-dependent) - -Create custom tracking rules for advanced configurations: - -**Sample Rate Rules:** -- Reduce data collection percentage for high-traffic sites -- Example: Track only 50% of visitors to reduce volume - -**Path-Based Rules:** -- Different behavior per route -- Example: Exclude `/admin/*` or `/internal/*` from tracking - -**Host-Based Rules:** -- Multi-domain configurations -- Example: Separate tracking for staging vs production subdomains - -**Availability:** Rules feature depends on your Cloudflare plan. Check dashboard under Web Analytics → Rules to see if available. Free plans may have limited or no access. - -## Plan Limits - -| Feature | Free | Notes | -|---------|------|-------| -| Proxied sites | Unlimited | DNS through Cloudflare | -| Non-proxied sites | 10 | External hosting | -| Pageviews | Unlimited | No volume limits | -| Data retention | 6 months | Rolling window | -| Rules | Plan-dependent | Check dashboard | - -## Privacy & Compliance - -- **No cookies** - Zero client-side storage -- **No fingerprinting** - No tracking across sites -- **No PII** - IP addresses not stored -- **GDPR-friendly** - Minimal data collection -- **CCPA-compliant** - No personal data sale - -**EU opt-out:** Dashboard option to exclude EU visitor data entirely. - -## Limitations - -- **Dashboard-only** - No API for programmatic access -- **No real-time** - 5-10 minute data delay -- **No custom events** - Automatic pageview/navigation tracking only -- **History API only** - Hash-based routing (`#/path`) not supported -- **No session replay** - Metrics only, no user recordings -- **No form tracking** - Page navigation tracking only - -## See Also - -- [Cloudflare Web Analytics Docs](https://developers.cloudflare.com/analytics/web-analytics/) -- [Core Web Vitals Guide](https://web.dev/vitals/) -- [GraphQL Analytics API Reference](../graphql-api/) - Query server-side analytics (HTTP, Workers, DNS, Firewall, etc.) via GraphQL diff --git a/external/develop/devops/cloudflare/references/web-analytics/configuration.md b/external/develop/devops/cloudflare/references/web-analytics/configuration.md deleted file mode 100644 index ff8f18d..0000000 --- a/external/develop/devops/cloudflare/references/web-analytics/configuration.md +++ /dev/null @@ -1,76 +0,0 @@ -# Configuration - -## Setup Methods - -### Proxied Sites (Automatic) - -Dashboard → Web Analytics → Add site → Select hostname → Done - -| Injection Option | Description | -|------------------|-------------| -| Enable | Auto-inject for all visitors (default) | -| Enable, excluding EU | No injection for EU (GDPR) | -| Enable with manual snippet | You add beacon manually | -| Disable | Pause tracking | - -**Fails if response has:** `Cache-Control: public, no-transform` - -**CSP required:** -``` -script-src https://static.cloudflareinsights.com https://cloudflareinsights.com; -``` - -### Non-Proxied Sites (Manual) - -Dashboard → Web Analytics → Add site → Enter hostname → Copy snippet - -```html - -``` - -**Limits:** 10 non-proxied sites per account - -## SPA Mode - -**Enable `spa: true` for:** React Router, Next.js, Vue Router, Nuxt, SvelteKit, Angular - -**Keep `spa: false` for:** Traditional multi-page apps, static sites, WordPress - -**Hash routing (`#/path`) NOT supported** - use History API routing. - -## Token Management - -- Found in: Dashboard → Web Analytics → Manage site -- **Not secrets** - domain-locked, safe to expose in HTML -- Each site gets unique token - -## Environment Config - -```typescript -// Only load in production -if (process.env.NODE_ENV === 'production') { - // Load beacon -} -``` - -Or use environment-specific tokens via env vars. - -## Verify Installation - -1. DevTools Network → filter `cloudflareinsights` → see `beacon.min.js` + data request -2. No CSP/CORS errors in console -3. Dashboard shows pageviews after 5-10 min delay - -## Rules (Plan-dependent) - -Configure in dashboard for: -- **Sample rate** - reduce collection % for high-traffic -- **Path-based** - different behavior per route -- **Host-based** - separate tracking per domain - -## Data Retention - -- 6 months rolling window -- 1-hour bucket granularity -- No raw export, dashboard only diff --git a/external/develop/devops/cloudflare/references/web-analytics/gotchas.md b/external/develop/devops/cloudflare/references/web-analytics/gotchas.md deleted file mode 100644 index cad1424..0000000 --- a/external/develop/devops/cloudflare/references/web-analytics/gotchas.md +++ /dev/null @@ -1,82 +0,0 @@ -# Web Analytics Gotchas - -## Critical Issues - -### SPA Navigation Not Tracked - -**Symptom:** Only initial pageload counted -**Fix:** Add `spa: true`: -```html - -``` - -### CSP Blocking Beacon - -**Symptom:** Console error "Refused to load script" -**Fix:** Allow both domains: -``` -script-src 'self' https://static.cloudflareinsights.com https://cloudflareinsights.com; -``` - -### Hash-Based Routing Unsupported - -**Symptom:** `#/path` URLs not tracked -**Fix:** Migrate to History API (`BrowserRouter`, not `HashRouter`). No workaround for hash routing. - -### No Data Appearing - -**Causes & Fixes:** -1. **Delay** - Wait 5-15 minutes -2. **Wrong token** - Verify matches dashboard exactly -3. **Script blocked** - Check DevTools Network tab for beacon.min.js -4. **Domain mismatch** - Dashboard site must match actual URL - -### Auto-Injection Fails - -**Cause:** `Cache-Control: no-transform` header -**Fix:** Remove `no-transform` or install beacon manually - -### Duplicate Pageviews - -**Cause:** Multiple beacon scripts -**Fix:** Keep only one beacon per page - -## Configuration Issues - -| Issue | Fix | -|-------|-----| -| 10-site limit reached | Delete old sites or proxy through CF (unlimited) | -| Token not recognized | Use exact alphanumeric token from dashboard | - -## Framework-Specific - -### Next.js Hydration Warning - -```tsx - -``` - -Place before closing `` tag. - -## Framework Examples - -| Framework | Location | Notes | -|-----------|----------|-------| -| React/Vite | `public/index.html` | Add `spa: true` | -| Next.js App Router | `app/layout.tsx` | Use ` -``` - -Without `spa: true`: only initial pageload tracked. - -## Staging/Production Separation - -```typescript -// Use env-specific tokens -const token = process.env.NEXT_PUBLIC_CF_ANALYTICS_TOKEN; -// .env.production: production token -// .env.staging: staging token (or empty to disable) -``` - -## Bot Filtering - -Dashboard → Filters → "Exclude Bot Traffic" - -Filters: Search crawlers, monitoring services, known bots. -Not filtered: Headless browsers (Playwright/Puppeteer). - -## Ad-Blocker Impact - -~25-40% of users may block `cloudflareinsights.com`. No official workaround. -Dashboard shows minimum baseline; use server logs for complete picture. - -## Limitations - -- No UTM parameter tracking -- No webhooks/alerts/API -- No custom beacon domains -- Max 10 non-proxied sites diff --git a/external/develop/devops/cloudflare/references/workerd/README.md b/external/develop/devops/cloudflare/references/workerd/README.md deleted file mode 100644 index 61115ce..0000000 --- a/external/develop/devops/cloudflare/references/workerd/README.md +++ /dev/null @@ -1,78 +0,0 @@ -# Workerd Runtime - -V8-based JS/Wasm runtime powering Cloudflare Workers. Use as app server, dev tool, or HTTP proxy. - -## ⚠️ IMPORTANT SECURITY NOTICE -**workerd is NOT a hardened sandbox.** Do not run untrusted code. It's designed for deploying YOUR code locally/self-hosted, not multi-tenant SaaS. Cloudflare production adds security layers not present in open-source workerd. - -## Decision Tree: When to Use What - -**95% of users:** Use Wrangler -- Local development: `wrangler dev` (uses workerd internally) -- Deployment: `wrangler deploy` (deploys to Cloudflare) -- Types: `wrangler types` (generates TypeScript types) - -**Use raw workerd directly only if:** -- Self-hosting Workers runtime in production -- Embedding runtime in C++ application -- Custom tooling/testing infrastructure -- Debugging workerd-specific behavior - -**Never use workerd for:** -- Running untrusted/user-submitted code -- Multi-tenant isolation (not hardened) -- Production without additional security layers - -## Key Features -- **Standards-based**: Fetch API, Web Crypto, Streams, WebSocket -- **Nanoservices**: Service bindings with local call performance -- **Capability security**: Explicit bindings prevent SSRF -- **Backwards compatible**: Version = max compat date supported - -## Architecture -``` -Config (workerd.capnp) -├── Services (workers/endpoints) -├── Sockets (HTTP/HTTPS listeners) -└── Extensions (global capabilities) -``` - -## Quick Start -```bash -workerd serve config.capnp -workerd compile config.capnp myConfig -o binary -workerd test config.capnp -``` - -## Platform Support & Beta Status - -| Platform | Status | Notes | -|----------|--------|-------| -| Linux (x64) | Stable | Primary platform | -| macOS (x64/ARM) | Stable | Full support | -| Windows | Beta | Use WSL2 for best results | -| Linux (ARM64) | Experimental | Limited testing | - -workerd is in **active development**. Breaking changes possible. Pin versions in production. - -## Core Concepts -- **Service**: Named endpoint (worker/network/disk/external) -- **Binding**: Capability-based resource access (KV/DO/R2/services) -- **Compatibility date**: Feature gate (always set!) -- **Modules**: ES modules (recommended) or service worker syntax - -## Reading Order (Progressive Disclosure) - -**Start here:** -1. This README (overview, decision tree) -2. [patterns.md](./patterns.md) - Common workflows, framework examples - -**When you need details:** -3. [configuration.md](./configuration.md) - Config format, services, bindings -4. [api.md](./api.md) - Runtime APIs, TypeScript types -5. [gotchas.md](./gotchas.md) - Common errors, debugging - -## Related References -- [workers](https://developers.cloudflare.com/workers/) - Workers runtime API documentation -- [miniflare](../miniflare/) - Testing tool built on workerd -- [wrangler](https://developers.cloudflare.com/workers/wrangler/) - CLI that uses workerd for local dev diff --git a/external/develop/devops/cloudflare/references/workerd/api.md b/external/develop/devops/cloudflare/references/workerd/api.md deleted file mode 100644 index 085f507..0000000 --- a/external/develop/devops/cloudflare/references/workerd/api.md +++ /dev/null @@ -1,185 +0,0 @@ -# Workerd APIs - -## Worker Code (JS/TS) - -### ES Modules (Recommended) -```javascript -export default { - async fetch(request, env, ctx) { - const value = await env.KV.get("key"); // Bindings in env - const response = await env.API.fetch(request); // Service binding - ctx.waitUntil(logRequest(request)); // Background task - return new Response("OK"); - }, - async adminApi(request, env, ctx) { /* Named entrypoint */ }, - async queue(batch, env, ctx) { /* Queue consumer */ }, - async scheduled(event, env, ctx) { /* Cron handler */ } -}; -``` - -### TypeScript Types - -**Generate from wrangler.toml (Recommended):** -```bash -wrangler types # Output: worker-configuration.d.ts -``` - -**Manual types:** -```typescript -interface Env { - API: Fetcher; - CACHE: KVNamespace; - STORAGE: R2Bucket; - ROOMS: DurableObjectNamespace; - API_KEY: string; -} - -export default { - async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { - return new Response(await env.CACHE.get("key")); - } -}; -``` - -**Setup:** -```bash -npm install -D @cloudflare/workers-types -``` - -```json -// tsconfig.json -{"compilerOptions": {"types": ["@cloudflare/workers-types"]}} -``` - -### Service Worker Syntax (Legacy) -```javascript -addEventListener('fetch', event => { - event.respondWith(handleRequest(event.request)); -}); - -async function handleRequest(request) { - const value = await KV.get("key"); // Bindings as globals - return new Response("OK"); -} -``` - -### Durable Objects -```javascript -export class Room { - constructor(state, env) { this.state = state; this.env = env; } - - async fetch(request) { - const url = new URL(request.url); - if (url.pathname === "/increment") { - const value = (await this.state.storage.get("counter")) || 0; - await this.state.storage.put("counter", value + 1); - return new Response(String(value + 1)); - } - return new Response("Not found", {status: 404}); - } -} -``` - -### RPC Between Services -```javascript -// Caller: env.AUTH.validateToken(token) returns structured data -const user = await env.AUTH.validateToken(request.headers.get("Authorization")); - -// Callee: export methods that return data -export default { - async validateToken(token) { return {id: 123, name: "Alice"}; } -}; -``` - -## Web Platform APIs - -### Fetch -- `fetch()`, `Request`, `Response`, `Headers` -- `AbortController`, `AbortSignal` - -### Streams -- `ReadableStream`, `WritableStream`, `TransformStream` -- Byte streams, BYOB readers - -### Web Crypto -- `crypto.subtle` (encrypt/decrypt/sign/verify) -- `crypto.randomUUID()`, `crypto.getRandomValues()` - -### Encoding -- `TextEncoder`, `TextDecoder` -- `atob()`, `btoa()` - -### Web Standards -- `URL`, `URLSearchParams` -- `Blob`, `File`, `FormData` -- `WebSocket` - -### Server-Sent Events (EventSource) -```javascript -// Server-side SSE -const { readable, writable } = new TransformStream(); -const writer = writable.getWriter(); -writer.write(new TextEncoder().encode('data: Hello\n\n')); -return new Response(readable, {headers: {'Content-Type': 'text/event-stream'}}); -``` - -### HTMLRewriter (HTML Parsing/Transformation) -```javascript -const response = await fetch('https://example.com'); -return new HTMLRewriter() - .on('a[href]', { - element(el) { - el.setAttribute('href', `/proxy?url=${encodeURIComponent(el.getAttribute('href'))}`); - } - }) - .on('script', { element(el) { el.remove(); } }) - .transform(response); -``` - -### TCP Sockets (Experimental) -```javascript -const socket = await connect({ hostname: 'example.com', port: 80 }); -const writer = socket.writable.getWriter(); -await writer.write(new TextEncoder().encode('GET / HTTP/1.1\r\n\r\n')); -const reader = socket.readable.getReader(); -const { value } = await reader.read(); -return new Response(value); -``` - -### Performance -- `performance.now()`, `performance.timeOrigin` -- `setTimeout()`, `setInterval()`, `queueMicrotask()` - -### Console -- `console.log()`, `console.error()`, `console.warn()` - -### Node.js Compat (`nodejs_compat` flag) -```javascript -import { Buffer } from 'node:buffer'; -import { randomBytes } from 'node:crypto'; - -const buf = Buffer.from('Hello'); -const random = randomBytes(16); -``` - -**Available:** `node:buffer`, `node:crypto`, `node:stream`, `node:util`, `node:events`, `node:assert`, `node:path`, `node:querystring`, `node:url` -**NOT available:** `node:fs`, `node:http`, `node:net`, `node:child_process` - -## CLI Commands - -```bash -workerd serve config.capnp [constantName] # Start server -workerd serve config.capnp --socket-addr http=*:3000 --verbose -workerd compile config.capnp constantName -o binary # Compile to binary -workerd test config.capnp [--test-only=test.js] # Run tests -``` - -## Wrangler Integration - -Use Wrangler for development: -```bash -wrangler dev # Uses workerd internally -wrangler types # Generate TypeScript types from wrangler.toml -``` - -See [patterns.md](./patterns.md) for usage examples, [configuration.md](./configuration.md) for config details. diff --git a/external/develop/devops/cloudflare/references/workerd/configuration.md b/external/develop/devops/cloudflare/references/workerd/configuration.md deleted file mode 100644 index bad5f43..0000000 --- a/external/develop/devops/cloudflare/references/workerd/configuration.md +++ /dev/null @@ -1,183 +0,0 @@ -# Workerd Configuration - -## Basic Structure -```capnp -using Workerd = import "/workerd/workerd.capnp"; - -const config :Workerd.Config = ( - services = [(name = "main", worker = .mainWorker)], - sockets = [(name = "http", address = "*:8080", http = (), service = "main")] -); - -const mainWorker :Workerd.Worker = ( - modules = [(name = "index.js", esModule = embed "src/index.js")], - compatibilityDate = "2024-01-15", - bindings = [...] -); -``` - -## Services -**Worker**: Run JS/Wasm code -```capnp -(name = "api", worker = ( - modules = [(name = "index.js", esModule = embed "index.js")], - compatibilityDate = "2024-01-15", - bindings = [...] -)) -``` - -**Network**: Internet access -```capnp -(name = "internet", network = (allow = ["public"], tlsOptions = (trustBrowserCas = true))) -``` - -**External**: Reverse proxy -```capnp -(name = "backend", external = (address = "api.com:443", http = (style = tls))) -``` - -**Disk**: Static files -```capnp -(name = "assets", disk = (path = "/var/www", writable = false)) -``` - -## Sockets -```capnp -(name = "http", address = "*:8080", http = (), service = "main") -(name = "https", address = "*:443", https = (options = (), tlsOptions = (keypair = (...))), service = "main") -(name = "app", address = "unix:/tmp/app.sock", http = (), service = "main") -``` - -## Worker Formats -```capnp -# ES Modules (recommended) -modules = [(name = "index.js", esModule = embed "src/index.js"), (name = "wasm.wasm", wasm = embed "build/module.wasm")] - -# Service Worker (legacy) -serviceWorkerScript = embed "worker.js" - -# CommonJS -(name = "legacy.js", commonJsModule = embed "legacy.js", namedExports = ["foo"]) -``` - -## Bindings -Bindings expose resources to workers. ES modules: `env.BINDING`, Service workers: globals. - -### Primitive Types -```capnp -(name = "API_KEY", text = "secret") # String -(name = "CONFIG", json = '{"key":"val"}') # Parsed JSON -(name = "DATA", data = embed "data.bin") # ArrayBuffer -(name = "DATABASE_URL", fromEnvironment = "DB_URL") # System env var -``` - -### Service Binding -```capnp -(name = "AUTH", service = "auth-worker") # Basic -(name = "API", service = ( - name = "backend", - entrypoint = "adminApi", # Named export - props = (json = '{"role":"admin"}') # ctx.props -)) -``` - -### Storage -```capnp -(name = "CACHE", kvNamespace = "kv-service") # KV -(name = "STORAGE", r2Bucket = "r2-service") # R2 -(name = "ROOMS", durableObjectNamespace = ( - serviceName = "room-service", - className = "Room" -)) -(name = "FAST", memoryCache = ( - id = "cache-id", - limits = (maxKeys = 1000, maxValueSize = 1048576) -)) -``` - -### Other -```capnp -(name = "TASKS", queue = "queue-service") -(name = "ANALYTICS", analyticsEngine = "analytics") -(name = "LOADER", workerLoader = (id = "dynamic")) -(name = "KEY", cryptoKey = (format = raw, algorithm = (name = "HMAC", hash = "SHA-256"), keyData = embed "key.bin", usages = [sign, verify], extractable = false)) -(name = "TRACED", wrapped = (moduleName = "tracing", entrypoint = "makeTracer", innerBindings = [(name = "backend", service = "backend")])) -``` - -## Compatibility -```capnp -compatibilityDate = "2024-01-15" # Always set! -compatibilityFlags = ["nodejs_compat", "streams_enable_constructors"] -``` - -Version = max compat date. Update carefully after testing. - -## Parameter Bindings (Inheritance) -```capnp -const base :Workerd.Worker = ( - modules = [...], compatibilityDate = "2024-01-15", - bindings = [(name = "API_URL", parameter = (type = text)), (name = "DB", parameter = (type = service))] -); - -const derived :Workerd.Worker = ( - inherit = "base-service", - bindings = [(name = "API_URL", text = "https://api.com"), (name = "DB", service = "postgres")] -); -``` - -## Durable Objects Config -```capnp -const worker :Workerd.Worker = ( - modules = [...], - compatibilityDate = "2024-01-15", - bindings = [(name = "ROOMS", durableObjectNamespace = "Room")], - durableObjectNamespaces = [(className = "Room", uniqueKey = "v1")], - durableObjectStorage = (localDisk = "/var/do") -); -``` - -## Remote Bindings (Development) - -Connect local workerd to production Cloudflare resources: - -```capnp -bindings = [ - # Remote KV (requires API token) - (name = "PROD_KV", kvNamespace = ( - remote = ( - accountId = "your-account-id", - namespaceId = "your-namespace-id", - apiToken = .envVar("CF_API_TOKEN") - ) - )), - - # Remote R2 - (name = "PROD_R2", r2Bucket = ( - remote = ( - accountId = "your-account-id", - bucketName = "my-bucket", - apiToken = .envVar("CF_API_TOKEN") - ) - )), - - # Remote Durable Object - (name = "PROD_DO", durableObjectNamespace = ( - remote = ( - accountId = "your-account-id", - scriptName = "my-worker", - className = "MyDO", - apiToken = .envVar("CF_API_TOKEN") - ) - )) -] -``` - -**Note:** Remote bindings require network access and valid Cloudflare API credentials. - -## Logging & Debugging -```capnp -logging = (structuredLogging = true, stdoutPrefix = "OUT: ", stderrPrefix = "ERR: ") -v8Flags = ["--expose-gc", "--max-old-space-size=2048"] # ⚠️ Unsupported in production -``` - -See [patterns.md](./patterns.md) for multi-service examples, [gotchas.md](./gotchas.md) for config errors. diff --git a/external/develop/devops/cloudflare/references/workerd/gotchas.md b/external/develop/devops/cloudflare/references/workerd/gotchas.md deleted file mode 100644 index dc35109..0000000 --- a/external/develop/devops/cloudflare/references/workerd/gotchas.md +++ /dev/null @@ -1,139 +0,0 @@ -# Workerd Gotchas - -## Common Errors - -### "Missing compatibility date" -**Cause:** Compatibility date not set -**Solution:** -❌ Wrong: -```capnp -const worker :Workerd.Worker = ( - serviceWorkerScript = embed "worker.js" -) -``` - -✅ Correct: -```capnp -const worker :Workerd.Worker = ( - serviceWorkerScript = embed "worker.js", - compatibilityDate = "2024-01-15" # Always set! -) -``` - -### Wrong Binding Type -**Problem:** JSON not parsed -**Cause:** Using `text = '{"key":"value"}'` instead of `json` -**Solution:** Use `json = '{"key":"value"}'` for parsed objects - -### Service vs Namespace -**Problem:** Cannot create DO instance -**Cause:** Using `service = "room-service"` for Durable Object -**Solution:** Use `durableObjectNamespace = "Room"` for DO bindings - -### Module Name Mismatch -**Problem:** Import fails -**Cause:** Module name includes path: `name = "src/index.js"` -**Solution:** Use simple names: `name = "index.js"`, embed with path - -## Network Access - -**Problem:** Fetch fails with network error -**Cause:** No network service configured (workerd has no global fetch) -**Solution:** Add network service binding: -```capnp -services = [(name = "internet", network = (allow = ["public"]))] -bindings = [(name = "NET", service = "internet")] -``` - -Or external service: -```capnp -bindings = [(name = "API", service = (external = (address = "api.com:443", http = (style = tls))))] -``` - -### "Worker not responding" -**Cause:** Socket misconfigured, no fetch handler, or port unavailable -**Solution:** Verify socket `address` matches, worker exports `fetch()`, port available - -### "Binding not found" -**Cause:** Name mismatch or service doesn't exist -**Solution:** Check binding name in config matches code (`env.BINDING` for ES modules) - -### "Module not found" -**Cause:** Module name doesn't match import or bad embed path -**Solution:** Module `name` must match import path exactly, verify `embed` path - -### "Compatibility error" -**Cause:** Date not set or API unavailable on that date -**Solution:** Set `compatibilityDate`, verify API available on that date - -## Performance Issues - -**Problem:** High memory usage -**Cause:** Large caches or many isolates -**Solution:** Set cache limits, reduce isolate count, or use V8 flags (caution) - -**Problem:** Slow startup -**Cause:** Many modules or complex config -**Solution:** Compile to binary (`workerd compile`), reduce imports - -**Problem:** Request timeouts -**Cause:** External service issues or DNS problems -**Solution:** Check connectivity, DNS resolution, TLS handshake - -## Build Issues - -**Problem:** Cap'n Proto syntax errors -**Cause:** Invalid config or missing schema -**Solution:** Install capnproto tools, validate: `capnp compile -I. config.capnp` - -**Problem:** Embed path not found -**Cause:** Path relative to config file -**Solution:** Use correct relative path or absolute path - -**Problem:** V8 flags cause crashes -**Cause:** Unsafe V8 flags -**Solution:** ⚠️ V8 flags unsupported in production. Test thoroughly before use. - -## Security Issues - -**Problem:** Hardcoded secrets in config -**Cause:** `text` binding with secret value -**Solution:** Use `fromEnvironment` to load from env vars - -**Problem:** Overly broad network access -**Cause:** `network = (allow = ["*"])` -**Solution:** Restrict to `allow = ["public"]` or specific hosts - -**Problem:** Extractable crypto keys -**Cause:** `cryptoKey = (extractable = true, ...)` -**Solution:** Set `extractable = false` unless export required - -## Compatibility Changes - -**Problem:** Breaking changes after compat date update -**Cause:** New flags enabled between dates -**Solution:** Review [compat dates docs](https://developers.cloudflare.com/workers/configuration/compatibility-dates/), test locally first - -**Problem:** "Compatibility date not supported" -**Cause:** Workerd version older than compat date -**Solution:** Update workerd binary (version = max compat date supported) - -## Limits - -| Resource/Limit | Value | Notes | -|----------------|-------|-------| -| V8 flags | Unsupported in production | Use with caution | -| Compatibility date | Must match workerd version | Update if mismatch | -| Module count | Affects startup time | Many imports slow | - -## Troubleshooting Steps - -1. **Enable verbose logging**: `workerd serve config.capnp --verbose` -2. **Check logs**: Look for error messages, stack traces -3. **Validate config**: `capnp compile -I. config.capnp` -4. **Test bindings**: Log `Object.keys(env)` to verify -5. **Check versions**: Workerd version vs compat date -6. **Isolate issue**: Minimal repro config -7. **Review schema**: [workerd.capnp](https://github.com/cloudflare/workerd/blob/main/src/workerd/server/workerd.capnp) - -See [configuration.md](./configuration.md) for config details, [patterns.md](./patterns.md) for working examples, [api.md](./api.md) for runtime APIs. diff --git a/external/develop/devops/cloudflare/references/workerd/patterns.md b/external/develop/devops/cloudflare/references/workerd/patterns.md deleted file mode 100644 index 5aaf092..0000000 --- a/external/develop/devops/cloudflare/references/workerd/patterns.md +++ /dev/null @@ -1,192 +0,0 @@ -# Workerd Patterns - -## Multi-Service Architecture -```capnp -const config :Workerd.Config = ( - services = [ - (name = "frontend", worker = ( - modules = [(name = "index.js", esModule = embed "frontend/index.js")], - compatibilityDate = "2024-01-15", - bindings = [(name = "API", service = "api")] - )), - (name = "api", worker = ( - modules = [(name = "index.js", esModule = embed "api/index.js")], - compatibilityDate = "2024-01-15", - bindings = [(name = "DB", service = "postgres"), (name = "CACHE", kvNamespace = "kv")] - )), - (name = "postgres", external = (address = "db.internal:5432", http = ())), - (name = "kv", disk = (path = "/var/kv", writable = true)) - ], - sockets = [(name = "http", address = "*:8080", http = (), service = "frontend")] -); -``` - -## Durable Objects -```capnp -const worker :Workerd.Worker = ( - modules = [(name = "index.js", esModule = embed "index.js"), (name = "room.js", esModule = embed "room.js")], - compatibilityDate = "2024-01-15", - bindings = [(name = "ROOMS", durableObjectNamespace = "Room")], - durableObjectNamespaces = [(className = "Room", uniqueKey = "v1")], - durableObjectStorage = (localDisk = "/var/do") -); -``` - -## Dev vs Prod Configs -```capnp -# Use parameter bindings for env-specific config -const baseWorker :Workerd.Worker = ( - modules = [(name = "index.js", esModule = embed "src/index.js")], - compatibilityDate = "2024-01-15", - bindings = [(name = "API_URL", parameter = (type = text))] -); - -const prodWorker :Workerd.Worker = ( - inherit = "base-service", - bindings = [(name = "API_URL", text = "https://api.prod.com")] -); -``` - -## HTTP Reverse Proxy -```capnp -services = [ - (name = "proxy", worker = (serviceWorkerScript = embed "proxy.js", compatibilityDate = "2024-01-15", bindings = [(name = "BACKEND", service = "backend")])), - (name = "backend", external = (address = "internal:8080", http = ())) -] -``` - -## Local Development - -**Recommended:** Use Wrangler -```bash -wrangler dev # Uses workerd internally -``` - -**Direct workerd:** -```bash -workerd serve config.capnp --socket-addr http=*:3000 --verbose -``` - -**Environment variables:** -```capnp -bindings = [(name = "DATABASE_URL", fromEnvironment = "DATABASE_URL")] -``` - -## Testing -```bash -workerd test config.capnp -workerd test config.capnp --test-only=test.js -``` - -Test files must be included in `modules = [...]` config. - -## Production Deployment - -### Compiled Binary (Recommended) -```bash -workerd compile config.capnp myConfig -o production-server -./production-server -``` - -### Docker -```dockerfile -FROM debian:bookworm-slim -RUN apt-get update && apt-get install -y ca-certificates -COPY workerd /usr/local/bin/ -COPY config.capnp /etc/workerd/ -COPY src/ /etc/workerd/src/ -EXPOSE 8080 -CMD ["workerd", "serve", "/etc/workerd/config.capnp"] -``` - -### Systemd -```ini -# /etc/systemd/system/workerd.service -[Service] -ExecStart=/usr/bin/workerd serve /etc/workerd/config.capnp --socket-fd http=3 -Restart=always -User=nobody -``` - -See systemd socket activation docs for complete setup. - -## Framework Integration - -### Hono -```javascript -import { Hono } from 'hono'; - -const app = new Hono(); - -app.get('/', (c) => c.text('Hello Hono!')); -app.get('/api/:id', async (c) => { - const id = c.req.param('id'); - const data = await c.env.KV.get(id); - return c.json({ id, data }); -}); - -export default app; -``` - -### itty-router -```javascript -import { Router } from 'itty-router'; - -const router = Router(); - -router.get('/', () => new Response('Hello itty!')); -router.get('/api/:id', async (request, env) => { - const { id } = request.params; - const data = await env.KV.get(id); - return Response.json({ id, data }); -}); - -export default { - fetch: (request, env, ctx) => router.handle(request, env, ctx) -}; -``` - -## Best Practices - -1. **Use ES modules** over service worker syntax -2. **Explicit bindings** - no global namespace assumptions -3. **Type safety** - define `Env` interfaces (use `wrangler types`) -4. **Service isolation** - split concerns into multiple services -5. **Pin compat date** in production after testing -6. **Use ctx.waitUntil()** for background tasks -7. **Handle errors gracefully** with try/catch -8. **Configure resource limits** on caches/storage - -## Common Patterns - -### Error Handling -```javascript -export default { - async fetch(request, env, ctx) { - try { - return await handleRequest(request, env); - } catch (error) { - console.error("Request failed", error); - return new Response("Internal Error", {status: 500}); - } - } -}; -``` - -### Background Tasks -```javascript -export default { - async fetch(request, env, ctx) { - const response = new Response("OK"); - - // Fire-and-forget background work - ctx.waitUntil( - env.ANALYTICS.put(request.url, Date.now()) - ); - - return response; - } -}; -``` - -See [configuration.md](./configuration.md) for config syntax, [api.md](./api.md) for runtime APIs, [gotchas.md](./gotchas.md) for common errors. diff --git a/external/develop/devops/cloudflare/references/workers-ai/README.md b/external/develop/devops/cloudflare/references/workers-ai/README.md index 7a2cf13..9e069e6 100644 --- a/external/develop/devops/cloudflare/references/workers-ai/README.md +++ b/external/develop/devops/cloudflare/references/workers-ai/README.md @@ -4,7 +4,7 @@ Use Workers AI for managed model inference from Workers or an external service. ## Choose a model -Start with the [model catalog](https://developers.cloudflare.com/workers-ai/models/) and open the selected model's page for its exact identifier, input/output schema, context window, and supported features. Compare candidates on the user's task, language, quality requirements, latency, and [current pricing](https://developers.cloudflare.com/workers-ai/platform/pricing/). Evaluate with representative inputs rather than treating model size as a quality or cost ranking. +Start with the [model catalog](https://developers.cloudflare.com/workers-ai/models/index.md) and open the selected model's page for its exact identifier, input/output schema, context window, and supported features. Compare candidates on the user's task, language, quality requirements, latency, and [current pricing](https://developers.cloudflare.com/workers-ai/platform/pricing/index.md). Evaluate with representative inputs rather than treating model size as a quality or cost ranking. For tool use, streaming, or structured output, confirm support for the selected model and integration. For embeddings, check output dimensions and compatibility with the existing index; changing the model may require re-embedding stored documents, even if dimensions match. @@ -21,4 +21,4 @@ If a topic is missing, use the [Workers AI documentation index](https://develope - [Vectorize](../vectorize/): vector storage and retrieval. - [AI Gateway](../ai-gateway/): inference analytics, caching, and request controls. -- [Workers](https://developers.cloudflare.com/workers/): runtime and application hosting. +- [Workers](https://developers.cloudflare.com/workers/index.md): runtime and application hosting. diff --git a/external/develop/devops/cloudflare/references/workers-ai/api.md b/external/develop/devops/cloudflare/references/workers-ai/api.md index 8b7ca9d..64c8f24 100644 --- a/external/develop/devops/cloudflare/references/workers-ai/api.md +++ b/external/develop/devops/cloudflare/references/workers-ai/api.md @@ -1,13 +1,13 @@ # Workers AI API -Fetch the selected model's page from the [model catalog](https://developers.cloudflare.com/workers-ai/models/) for request fields, output format, dimensions, and examples. Text, embeddings, images, audio, and translation do not share one response schema. +Fetch the selected model's page from the [model catalog](https://developers.cloudflare.com/workers-ai/models/index.md) for request fields, output format, dimensions, and examples. Text, embeddings, images, audio, and translation do not share one response schema. | Task | Documentation | |------|---------------| -| Invoke inference through a Worker binding | [Workers bindings](https://developers.cloudflare.com/workers-ai/configuration/bindings/) | -| Invoke inference over HTTP | [REST API reference](https://developers.cloudflare.com/api/resources/ai/methods/run/) | -| Stream text or use SDK abstractions | [Vercel AI SDK](https://developers.cloudflare.com/workers-ai/configuration/ai-sdk/), or the selected model's streaming example | -| Define tools and handle tool results | [Function calling](https://developers.cloudflare.com/workers-ai/features/function-calling/) | -| Request structured output | [JSON mode](https://developers.cloudflare.com/workers-ai/features/json-mode/) | +| Invoke inference through a Worker binding | [Workers bindings](https://developers.cloudflare.com/workers-ai/configuration/bindings/index.md) | +| Invoke inference over HTTP | [REST API reference](https://developers.cloudflare.com/api/resources/ai/methods/run/index.md) | +| Stream text or use SDK abstractions | [Vercel AI SDK](https://developers.cloudflare.com/workers-ai/configuration/ai-sdk/index.md), or the selected model's streaming example | +| Define tools and handle tool results | [Function calling](https://developers.cloudflare.com/workers-ai/features/function-calling/index.md) | +| Request structured output | [JSON mode](https://developers.cloudflare.com/workers-ai/features/json-mode/index.md) | Use the response and stream format documented for the chosen integration. Do not assume native binding streams are parsed objects or apply OpenAI response parsing to every model. Check the model's batching support and limits before combining inputs in one request. diff --git a/external/develop/devops/cloudflare/references/workers-ai/configuration.md b/external/develop/devops/cloudflare/references/workers-ai/configuration.md index f7ec4c4..c561de4 100644 --- a/external/develop/devops/cloudflare/references/workers-ai/configuration.md +++ b/external/develop/devops/cloudflare/references/workers-ai/configuration.md @@ -4,12 +4,12 @@ Read the setup guide for the application's existing integration and installed SD | Task | Documentation | |------|---------------| -| Create and develop a Worker with Workers AI | [Workers and Wrangler setup](https://developers.cloudflare.com/workers-ai/get-started/workers-wrangler/) | -| Add an AI binding to an existing Worker | [Workers bindings](https://developers.cloudflare.com/workers-ai/configuration/bindings/) | -| Generate environment and runtime types | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/) | -| Call inference from outside Workers | [REST API setup and authentication](https://developers.cloudflare.com/workers-ai/get-started/rest-api/) | -| Use the Vercel AI SDK | [AI SDK integration](https://developers.cloudflare.com/workers-ai/configuration/ai-sdk/) | -| Adapt an existing OpenAI SDK client | [OpenAI compatible endpoints](https://developers.cloudflare.com/workers-ai/configuration/open-ai-compatibility/) | +| Create and develop a Worker with Workers AI | [Workers and Wrangler setup](https://developers.cloudflare.com/workers-ai/get-started/workers-wrangler/index.md) | +| Add an AI binding to an existing Worker | [Workers bindings](https://developers.cloudflare.com/workers-ai/configuration/bindings/index.md) | +| Generate environment and runtime types | [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/index.md) | +| Call inference from outside Workers | [REST API setup and authentication](https://developers.cloudflare.com/workers-ai/get-started/rest-api/index.md) | +| Use the Vercel AI SDK | [AI SDK integration](https://developers.cloudflare.com/workers-ai/configuration/ai-sdk/index.md) | +| Adapt an existing OpenAI SDK client | [OpenAI compatible endpoints](https://developers.cloudflare.com/workers-ai/configuration/open-ai-compatibility/index.md) | Prefer the native binding for a Worker that does not need an SDK abstraction; use REST for external services. Preserve an existing SDK integration when it meets the task, and check its supported endpoints and model features before substituting providers. diff --git a/external/develop/devops/cloudflare/references/workers-ai/gotchas.md b/external/develop/devops/cloudflare/references/workers-ai/gotchas.md index 87aaad6..1b6b919 100644 --- a/external/develop/devops/cloudflare/references/workers-ai/gotchas.md +++ b/external/develop/devops/cloudflare/references/workers-ai/gotchas.md @@ -4,12 +4,12 @@ Use the actual error, model identifier, integration, and installed versions to c | Symptom or decision | Documentation and checks | |---------------------|--------------------------| -| Missing binding or types | [Binding configuration](https://developers.cloudflare.com/workers-ai/configuration/bindings/) and [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/); check the environment being run | -| Development inference fails | [Workers and Wrangler setup](https://developers.cloudflare.com/workers-ai/get-started/workers-wrangler/); check account access and binding setup | -| Unknown model, invalid input, or unexpected response | Open the exact model in the [catalog](https://developers.cloudflare.com/workers-ai/models/); check its schema, context window, and feature support | -| Inference error or retry decision | [Error codes and HTTP statuses](https://developers.cloudflare.com/workers-ai/platform/errors/) | -| Throttling or concurrency planning | [Current limits](https://developers.cloudflare.com/workers-ai/platform/limits/) | -| Usage or cost estimate | [Current pricing](https://developers.cloudflare.com/workers-ai/platform/pricing/); use the selected model's billing units and expected workload | -| Old SDK examples fail | [Native binding](https://developers.cloudflare.com/workers-ai/configuration/bindings/), [AI SDK](https://developers.cloudflare.com/workers-ai/configuration/ai-sdk/), or [OpenAI compatibility](https://developers.cloudflare.com/workers-ai/configuration/open-ai-compatibility/), according to the integration | +| Missing binding or types | [Binding configuration](https://developers.cloudflare.com/workers-ai/configuration/bindings/index.md) and [Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/index.md); check the environment being run | +| Development inference fails | [Workers and Wrangler setup](https://developers.cloudflare.com/workers-ai/get-started/workers-wrangler/index.md); check account access and binding setup | +| Unknown model, invalid input, or unexpected response | Open the exact model in the [catalog](https://developers.cloudflare.com/workers-ai/models/index.md); check its schema, context window, and feature support | +| Inference error or retry decision | [Error codes and HTTP statuses](https://developers.cloudflare.com/workers-ai/platform/errors/index.md) | +| Throttling or concurrency planning | [Current limits](https://developers.cloudflare.com/workers-ai/platform/limits/index.md) | +| Usage or cost estimate | [Current pricing](https://developers.cloudflare.com/workers-ai/platform/pricing/index.md); use the selected model's billing units and expected workload | +| Old SDK examples fail | [Native binding](https://developers.cloudflare.com/workers-ai/configuration/bindings/index.md), [AI SDK](https://developers.cloudflare.com/workers-ai/configuration/ai-sdk/index.md), or [OpenAI compatibility](https://developers.cloudflare.com/workers-ai/configuration/open-ai-compatibility/index.md), according to the integration | Do not copy an error-code mapping, per-request neuron estimate, or context-window range from another model or an older example. Measure latency for the intended workload rather than promising a fixed cold-start or inference time. diff --git a/external/develop/devops/cloudflare/references/workers-ai/patterns.md b/external/develop/devops/cloudflare/references/workers-ai/patterns.md index 18cc0bb..bd946ea 100644 --- a/external/develop/devops/cloudflare/references/workers-ai/patterns.md +++ b/external/develop/devops/cloudflare/references/workers-ai/patterns.md @@ -4,10 +4,10 @@ Use direct generation when the supplied context fits the selected model and retr | Task | Documentation | |------|---------------| -| Build retrieval with Workers AI, Vectorize, and document storage | [RAG tutorial](https://developers.cloudflare.com/workers-ai/guides/tutorials/build-a-retrieval-augmented-generation-ai/) | -| Stream responses or integrate tool calling in an SDK application | [AI SDK integration](https://developers.cloudflare.com/workers-ai/configuration/ai-sdk/) | -| Constrain generated JSON | [JSON mode](https://developers.cloudflare.com/workers-ai/features/json-mode/) | -| Add caching, retries, or model fallbacks | [Caching](https://developers.cloudflare.com/ai-gateway/features/caching/), [request handling](https://developers.cloudflare.com/ai-gateway/configuration/request-handling/), and [dynamic routing](https://developers.cloudflare.com/ai-gateway/features/dynamic-routing/) | +| Build retrieval with Workers AI, Vectorize, and document storage | [RAG tutorial](https://developers.cloudflare.com/workers-ai/guides/tutorials/build-a-retrieval-augmented-generation-ai/index.md) | +| Stream responses or integrate tool calling in an SDK application | [AI SDK integration](https://developers.cloudflare.com/workers-ai/configuration/ai-sdk/index.md) | +| Constrain generated JSON | [JSON mode](https://developers.cloudflare.com/workers-ai/features/json-mode/index.md) | +| Add caching, retries, or model fallbacks | [Caching](https://developers.cloudflare.com/ai-gateway/features/caching/index.md), [request handling](https://developers.cloudflare.com/ai-gateway/configuration/request-handling/index.md), and [dynamic routing](https://developers.cloudflare.com/ai-gateway/features/dynamic-routing/index.md) | Treat tutorial models as examples; select models using the [model criteria](./README.md#choose-a-model). For RAG, embed queries and documents with compatible models and match the index dimensions to the embeddings. Budget for retrieval and embedding work as well as generation. diff --git a/external/develop/devops/cloudflare/references/workers-for-platforms/README.md b/external/develop/devops/cloudflare/references/workers-for-platforms/README.md deleted file mode 100644 index e8dde49..0000000 --- a/external/develop/devops/cloudflare/references/workers-for-platforms/README.md +++ /dev/null @@ -1,89 +0,0 @@ -# Cloudflare Workers for Platforms - -Multi-tenant platform with isolated customer code execution at scale. - -## Use Cases - -- Multi-tenant SaaS running customer code -- AI-generated code execution in secure sandboxes -- Programmable platforms with isolated compute -- Edge functions/serverless platforms -- Website builders with static + dynamic content -- Unlimited app deployment at scale - -**NOT for general Workers** - only for Workers for Platforms architecture. - -## Quick Start - -**One-click deploy:** [Platform Starter Kit](https://github.com/cloudflare/workers-for-platforms-example) deploys complete WfP setup with dispatch namespace, dispatch worker, and user worker example. - -[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/workers-for-platforms-example) - -**Manual setup:** See [configuration.md](./configuration.md) for namespace creation and dispatch worker configuration. - -## Key Features - -- Unlimited Workers per namespace (no script limits) -- Automatic tenant isolation -- Custom CPU/subrequest limits per customer -- Hostname routing (subdomains/vanity domains) -- Egress/ingress control -- Static assets support -- Tags for bulk operations - -## Architecture - -**4 Components:** -1. **Dispatch Namespace** - Container for unlimited customer Workers, automatic isolation (untrusted mode by default - no request.cf access, no shared cache) -2. **Dynamic Dispatch Worker** - Entry point, routes requests, enforces platform logic (auth, limits, validation) -3. **User Workers** - Customer code in isolated sandboxes, API-deployed, optional bindings (KV/D1/R2/DO) -4. **Outbound Worker** (optional) - Intercepts external fetch, controls egress, logs subrequests (blocks TCP socket connect() API) - -**Request Flow:** -``` -Request → Dispatch Worker → Determines user Worker → env.DISPATCHER.get("customer") -→ User Worker executes (Outbound Worker for external fetch) → Response → Dispatch Worker → Client -``` - -## Decision Trees - -### When to Use Workers for Platforms -``` -Need to run code? -├─ Your code only → Regular Workers -├─ Customer/AI code → Workers for Platforms -└─ Untrusted code in sandbox → Workers for Platforms OR Sandbox API -``` - -### Routing Strategy Selection -``` -Hostname routing needed? -├─ Subdomains only (*.saas.com) → `*.saas.com/*` route + subdomain extraction -├─ Custom domains → `*/*` wildcard + Cloudflare for SaaS + KV/metadata routing -└─ Path-based (/customer/app) → Any route + path parsing -``` - -### Isolation Mode Selection -``` -Worker mode? -├─ Running customer code → Untrusted (default) -├─ Need request.cf geolocation → Trusted mode -├─ Internal platform, controlled code → Trusted mode with cache key prefixes -└─ Maximum isolation → Untrusted + unique resources per customer -``` - -## In This Reference - -| File | Purpose | When to Read | -|------|---------|--------------| -| [configuration.md](./configuration.md) | Namespace setup, dispatch worker config | First-time setup, changing limits | -| [api.md](./api.md) | User worker API, dispatch API, outbound worker | Deploying workers, SDK integration | -| [patterns.md](./patterns.md) | Multi-tenancy, routing, egress control | Planning architecture, scaling | -| [gotchas.md](./gotchas.md) | Limits, isolation issues, best practices | Debugging, production prep | - -## See Also -- [workers](https://developers.cloudflare.com/workers/) - Core Workers runtime documentation -- [durable-objects](https://developers.cloudflare.com/durable-objects/) - Stateful multi-tenant patterns -- [sandbox](https://developers.cloudflare.com/sandbox/) - Alternative for untrusted code execution -- [Reference Architecture: Programmable Platforms](https://developers.cloudflare.com/reference-architecture/diagrams/serverless/programmable-platforms/) -- [Reference Architecture: AI Vibe Coding Platform](https://developers.cloudflare.com/reference-architecture/diagrams/ai/ai-vibe-coding-platform/) diff --git a/external/develop/devops/cloudflare/references/workers-for-platforms/api.md b/external/develop/devops/cloudflare/references/workers-for-platforms/api.md deleted file mode 100644 index 663c608..0000000 --- a/external/develop/devops/cloudflare/references/workers-for-platforms/api.md +++ /dev/null @@ -1,196 +0,0 @@ -# API Operations - -## Deploy User Worker - -```bash -curl -X PUT \ - "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/dispatch/namespaces/$NAMESPACE/scripts/$SCRIPT_NAME" \ - -H "Authorization: Bearer $API_TOKEN" \ - -F 'metadata={"main_module": "worker.mjs"};type=application/json' \ - -F 'worker.mjs=@worker.mjs;type=application/javascript+module' -``` - -### TypeScript SDK -```typescript -import Cloudflare from "cloudflare"; - -const client = new Cloudflare({ apiToken: process.env.API_TOKEN }); - -const scriptFile = new File([scriptContent], `${scriptName}.mjs`, { - type: "application/javascript+module", -}); - -await client.workersForPlatforms.dispatch.namespaces.scripts.update( - namespace, scriptName, - { - account_id: accountId, - metadata: { main_module: `${scriptName}.mjs` }, - files: [scriptFile], - } -); -``` - -## TypeScript Types - -```typescript -import type { DispatchNamespace } from '@cloudflare/workers-types'; - -interface DispatchNamespace { - get(name: string, options?: Record, dispatchOptions?: DynamicDispatchOptions): Fetcher; -} - -interface DynamicDispatchOptions { - limits?: DynamicDispatchLimits; - outbound?: Record; -} - -interface DynamicDispatchLimits { - cpuMs?: number; // Max CPU milliseconds - subRequests?: number; // Max fetch() calls -} - -// Usage -const userWorker = env.DISPATCHER.get('customer-123', {}, { - limits: { cpuMs: 50, subRequests: 20 }, - outbound: { customerId: '123', url: request.url } -}); -``` - -## Deploy with Bindings -```bash -curl -X PUT ".../scripts/$SCRIPT_NAME" \ - -F 'metadata={ - "main_module": "worker.mjs", - "bindings": [ - {"type": "kv_namespace", "name": "MY_KV", "namespace_id": "'$KV_ID'"} - ], - "tags": ["customer-123", "production"], - "compatibility_date": "2026-01-01" // Use current date for new projects - };type=application/json' \ - -F 'worker.mjs=@worker.mjs;type=application/javascript+module' -``` - -## List/Delete Workers - -```bash -# List -curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/dispatch/namespaces/$NAMESPACE/scripts" \ - -H "Authorization: Bearer $API_TOKEN" - -# Delete by name -curl -X DELETE ".../scripts/$SCRIPT_NAME" -H "Authorization: Bearer $API_TOKEN" - -# Delete by tag -curl -X DELETE ".../scripts?tags=customer-123%3Ayes" -H "Authorization: Bearer $API_TOKEN" -``` - -**Pagination:** SDK supports async iteration. Manual: add `?per_page=100&page=1` query params. - -## Static Assets - -**3-step process:** Create session → Upload files → Deploy Worker - -### 1. Create Upload Session -```bash -curl -X POST ".../scripts/$SCRIPT_NAME/assets-upload-session" \ - -H "Authorization: Bearer $API_TOKEN" \ - -d '{ - "manifest": { - "/index.html": {"hash": "08f1dfda4574284ab3c21666d1ee8c7d4", "size": 1234} - } - }' -# Returns: jwt, buckets -``` - -**Hash:** SHA-256 truncated to first 16 bytes (32 hex characters) - -### 2. Upload Files -```bash -curl -X POST ".../workers/assets/upload?base64=true" \ - -H "Authorization: Bearer $UPLOAD_JWT" \ - -F '08f1dfda4574284ab3c21666d1ee8c7d4=' -# Returns: completion jwt -``` - -**Multiple buckets:** Upload to all returned bucket URLs (typically 2 for redundancy) using same JWT and hash. - -### 3. Deploy with Assets -```bash -curl -X PUT ".../scripts/$SCRIPT_NAME" \ - -F 'metadata={ - "main_module": "index.js", - "assets": {"jwt": ""}, - "bindings": [{"type": "assets", "name": "ASSETS"}] - };type=application/json' \ - -F 'index.js=export default {...};type=application/javascript+module' -``` - -**Asset Isolation:** Assets shared across namespace by default. For customer isolation, salt hash: `sha256(customerId + fileContents).slice(0, 32)` - -## Dispatch Workers - -### Subdomain Routing -```typescript -export default { - async fetch(request: Request, env: Env): Promise { - const userWorkerName = new URL(request.url).hostname.split(".")[0]; - const userWorker = env.DISPATCHER.get(userWorkerName); - return await userWorker.fetch(request); - }, -}; -``` - -### Path Routing -```typescript -const pathParts = new URL(request.url).pathname.split("/").filter(Boolean); -const userWorker = env.DISPATCHER.get(pathParts[0]); -return await userWorker.fetch(request); -``` - -### KV Routing -```typescript -const hostname = new URL(request.url).hostname; -const userWorkerName = await env.ROUTING_KV.get(hostname); -const userWorker = env.DISPATCHER.get(userWorkerName); -return await userWorker.fetch(request); -``` - -## Outbound Workers - -Control external fetch from user Workers: - -### Configure -```typescript -const userWorker = env.DISPATCHER.get( - workerName, {}, - { outbound: { customer_context: { customer_name: workerName, url: request.url } } } -); -``` - -### Implement -```typescript -export default { - async fetch(request: Request, env: Env): Promise { - const customerName = env.customer_name; - const url = new URL(request.url); - - // Block domains - if (["malicious.com"].some(d => url.hostname.includes(d))) { - return new Response("Blocked", { status: 403 }); - } - - // Inject auth - if (url.hostname === "api.example.com") { - const headers = new Headers(request.headers); - headers.set("Authorization", `Bearer ${generateJWT(customerName)}`); - return fetch(new Request(request, { headers })); - } - - return fetch(request); - }, -}; -``` - -**Note:** Doesn't intercept DO/mTLS fetch. - -See [README.md](./README.md), [configuration.md](./configuration.md), [patterns.md](./patterns.md), [gotchas.md](./gotchas.md) diff --git a/external/develop/devops/cloudflare/references/workers-for-platforms/configuration.md b/external/develop/devops/cloudflare/references/workers-for-platforms/configuration.md deleted file mode 100644 index b434999..0000000 --- a/external/develop/devops/cloudflare/references/workers-for-platforms/configuration.md +++ /dev/null @@ -1,167 +0,0 @@ -# Configuration - -## Dispatch Namespace Binding - -### wrangler.jsonc -```jsonc -{ - "$schema": "./node_modules/wrangler/config-schema.json", - "dispatch_namespaces": [{ - "binding": "DISPATCHER", - "namespace": "production" - }] -} -``` - -## Worker Isolation Mode - -Workers in a namespace run in **untrusted mode** by default for security: -- No access to `request.cf` object -- Isolated cache per Worker (no shared cache) -- `caches.default` disabled - -### Enable Trusted Mode - -For internal platforms where you control all code: - -```bash -curl -X PUT \ - "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/dispatch/namespaces/$NAMESPACE" \ - -H "Authorization: Bearer $API_TOKEN" \ - -d '{"name": "'$NAMESPACE'", "trusted_workers": true}' -``` - -**Caveats:** -- Workers share cache within namespace (use cache key prefixes: `customer-${id}:${key}`) -- `request.cf` object accessible -- Redeploy existing Workers after enabling trusted mode - -**When to use:** Internal platforms, A/B testing platforms, need geolocation data - - -### With Outbound Worker -```jsonc -{ - "dispatch_namespaces": [{ - "binding": "DISPATCHER", - "namespace": "production", - "outbound": { - "service": "outbound-worker", - "parameters": ["customer_context"] - } - }] -} -``` - -## Wrangler Commands - -```bash -wrangler dispatch-namespace list -wrangler dispatch-namespace get production -wrangler dispatch-namespace create production -wrangler dispatch-namespace delete staging -wrangler dispatch-namespace rename old new -``` - -## Custom Limits - -Set CPU time and subrequest limits per invocation: - -```typescript -const userWorker = env.DISPATCHER.get( - workerName, - {}, - { - limits: { - cpuMs: 10, // Max CPU ms - subRequests: 5 // Max fetch() calls - } - } -); -``` - -Handle limit violations: -```typescript -try { - return await userWorker.fetch(request); -} catch (e) { - if (e.message.includes("CPU time limit")) { - return new Response("CPU limit exceeded", { status: 429 }); - } - throw e; -} -``` - -## Static Assets - -Deploy HTML/CSS/images with Workers. See [api.md](./api.md#static-assets) for upload process. - -### Wrangler -```jsonc -{ - "name": "customer-site", - "main": "./src/index.js", - "assets": { - "directory": "./public", - "binding": "ASSETS" - } -} -``` - -```bash -npx wrangler deploy --name customer-site --dispatch-namespace production -``` - -### Dashboard Deployment - -Alternative to CLI: - -1. Upload Worker file in dashboard -2. Add `--dispatch-namespace` flag: `wrangler deploy --dispatch-namespace production` -3. Or configure in wrangler.jsonc under `dispatch_namespaces` - -See [api.md](./api.md) for programmatic deployment via REST API or SDK. - -## Tags - -Organize/search Workers (max 8/script): - -```bash -# Set tags -curl -X PUT ".../tags" -d '["customer-123", "pro", "production"]' - -# Filter by tag -curl ".../scripts?tags=production%3Ayes" - -# Delete by tag -curl -X DELETE ".../scripts?tags=customer-123%3Ayes" -``` - -Common patterns: `customer-123`, `free|pro|enterprise`, `production|staging` - -## Bindings - -**Supported binding types:** 29 total including KV, D1, R2, Durable Objects, Analytics Engine, Service, Assets, Queue, Vectorize, Hyperdrive, Workflow, AI, Browser, and more. - -Add via API metadata (see [api.md](./api.md#deploy-with-bindings)): -```json -{ - "bindings": [ - {"type": "kv_namespace", "name": "USER_KV", "namespace_id": "..."}, - {"type": "r2_bucket", "name": "STORAGE", "bucket_name": "..."}, - {"type": "d1", "name": "DB", "id": "..."} - ] -} -``` - -Preserve existing bindings: -```json -{ - "bindings": [{"type": "r2_bucket", "name": "STORAGE", "bucket_name": "new"}], - "keep_bindings": ["kv_namespace", "d1"] // Preserves existing bindings of these types -} -``` - -For complete binding type reference, see [bindings](../bindings/) documentation - -See [README.md](./README.md), [api.md](./api.md), [patterns.md](./patterns.md), [gotchas.md](./gotchas.md) diff --git a/external/develop/devops/cloudflare/references/workers-for-platforms/gotchas.md b/external/develop/devops/cloudflare/references/workers-for-platforms/gotchas.md deleted file mode 100644 index a32fe18..0000000 --- a/external/develop/devops/cloudflare/references/workers-for-platforms/gotchas.md +++ /dev/null @@ -1,134 +0,0 @@ -# Gotchas & Limits - -## Common Errors - -### "Worker not found" - -**Cause:** Attempting to get Worker that doesn't exist in namespace -**Solution:** Catch error and return 404: - -```typescript -try { - const userWorker = env.DISPATCHER.get(workerName); - return userWorker.fetch(request); -} catch (e) { - if (e.message.startsWith("Worker not found")) { - return new Response("Worker not found", { status: 404 }); - } - throw e; // Re-throw unexpected errors -} -``` - -### "CPU time limit exceeded" - -**Cause:** User Worker exceeded configured CPU time limit -**Solution:** Track violations in Analytics Engine and return 429 response; consider adjusting limits per customer tier - -### "Hostname Routing Issues" - -**Cause:** DNS proxy settings causing routing problems -**Solution:** Use `*/*` wildcard route which works regardless of proxy settings for orange-to-orange routing - -### "Bindings Lost on Update" - -**Cause:** Not using `keep_bindings` flag when updating Worker -**Solution:** Use `keep_bindings: true` in API requests to preserve existing bindings during updates - -### "Tag Filtering Not Working" - -**Cause:** Special characters not URL encoded in tag filters -**Solution:** URL encode tags (e.g., `tags=production%3Ayes`) and avoid special chars like `,` and `&` - -### "Deploy Failures with ES Modules" - -**Cause:** Incorrect upload format for ES modules -**Solution:** Use multipart form upload, specify `main_module` in metadata, and set file type to `application/javascript+module` - -### "Static Asset Upload Failed" - -**Cause:** Invalid hash format, expired token, or incorrect encoding -**Solution:** Hash must be first 16 bytes (32 hex chars) of SHA-256, upload within 1 hour of session creation, deploy within 1 hour of upload completion, and Base64 encode file contents - -### "Outbound Worker Not Intercepting Calls" - -**Cause:** Outbound Workers don't intercept Durable Object or mTLS binding fetch -**Solution:** Plan egress control accordingly; not all fetch calls are intercepted - -### "TCP Socket Connection Failed" - -**Cause:** Outbound Worker enabled blocks `connect()` API for TCP sockets -**Solution:** Outbound Workers only intercept `fetch()` calls; TCP socket connections unavailable when outbound configured. Remove outbound if TCP needed, or use proxy pattern. - -### "API Rate Limit Exceeded" - -**Cause:** Exceeded Cloudflare API rate limits (1200 requests per 5 minutes per account, 200 requests per second per IP) -**Solution:** Implement exponential backoff: - -```typescript -async function deployWithBackoff(deploy: () => Promise, maxRetries = 3) { - for (let i = 0; i < maxRetries; i++) { - try { - return await deploy(); - } catch (e) { - if (e.status === 429 && i < maxRetries - 1) { - await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000)); - continue; - } - throw e; - } - } -} -``` - -### "Gradual Deployment Not Supported" - -**Cause:** Attempted to use gradual deployments with user Workers -**Solution:** Gradual deployments not supported for Workers in dispatch namespaces. Use all-at-once deployment with staged rollout via dispatch worker logic (feature flags, percentage-based routing). - -### "Asset Session Expired" - -**Cause:** Upload JWT expired (1 hour validity) or completion token expired (1 hour after upload) -**Solution:** Complete asset upload within 1 hour of session creation, and deploy Worker within 1 hour of upload completion. For large uploads, batch files or increase upload parallelism. - -## Platform Limits - -| Limit | Value | Notes | -|-------|-------|-------| -| Workers per namespace | Unlimited | Unlike regular Workers (500 per account) | -| Namespaces per account | Unlimited | Best practice: 1 production + 1 staging | -| Max tags per Worker | 8 | For filtering and organization | -| Worker mode | Untrusted (default) | No `request.cf` access unless trusted mode | -| Cache isolation | Per-Worker (untrusted) | Shared in trusted mode with key prefixes | -| Durable Object namespaces | Unlimited | No per-account limit for WfP | -| Gradual Deployments | Not supported | All-at-once only | -| `caches.default` | Disabled (untrusted) | Use Cache API with custom keys | - -## Asset Upload Limits - -| Limit | Value | Notes | -|-------|-------|-------| -| Upload session JWT validity | 1 hour | Must complete upload within this time | -| Completion token validity | 1 hour | Must deploy within this time after upload | -| Asset hash format | First 16 bytes SHA-256 | 32 hex characters | -| Base64 encoding | Required | For binary files | - -## API Rate Limits - -| Limit Type | Value | Scope | -|------------|-------|-------| -| Client API | 1200 requests / 5 min | Per account | -| Client API | 200 requests / sec | Per IP address | -| GraphQL | Varies by query cost | Query complexity | - -See [Cloudflare API Rate Limits](https://developers.cloudflare.com/fundamentals/api/reference/limits/) for details. - -## Operational Limits - -| Operation | Limit | Notes | -|-----------|-------|-------| -| CPU time (custom limits) | Up to Workers plan limit | Set per-invocation in dispatch worker | -| Subrequests (custom limits) | Up to Workers plan limit | Set per-invocation in dispatch worker | -| Outbound Worker subrequests | Not intercepted for DO/mTLS | Only regular fetch() calls | -| TCP sockets with outbound | Disabled | `connect()` API unavailable | - -See [README.md](./README.md), [configuration.md](./configuration.md), [api.md](./api.md), [patterns.md](./patterns.md) diff --git a/external/develop/devops/cloudflare/references/workers-for-platforms/patterns.md b/external/develop/devops/cloudflare/references/workers-for-platforms/patterns.md deleted file mode 100644 index d198430..0000000 --- a/external/develop/devops/cloudflare/references/workers-for-platforms/patterns.md +++ /dev/null @@ -1,188 +0,0 @@ -# Multi-Tenant Patterns - -## Billing by Plan - -```typescript -interface Env { - DISPATCHER: DispatchNamespace; - CUSTOMERS_KV: KVNamespace; -} - -export default { - async fetch(request: Request, env: Env): Promise { - const userWorkerName = new URL(request.url).hostname.split(".")[0]; - const customerPlan = await env.CUSTOMERS_KV.get(userWorkerName); - - const plans = { - enterprise: { cpuMs: 50, subRequests: 50 }, - pro: { cpuMs: 20, subRequests: 20 }, - free: { cpuMs: 10, subRequests: 5 }, - }; - const limits = plans[customerPlan as keyof typeof plans] || plans.free; - - const userWorker = env.DISPATCHER.get(userWorkerName, {}, { limits }); - return await userWorker.fetch(request); - }, -}; -``` - -## Resource Isolation - -**Complete isolation:** Create unique resources per customer -- KV namespace per customer -- D1 database per customer -- R2 bucket per customer - -```typescript -const bindings = [{ - type: "kv_namespace", - name: "USER_KV", - namespace_id: `customer-${customerId}-kv` -}]; -``` - -## Hostname Routing - -### Wildcard Route (Recommended) -Configure `*/*` route on SaaS domain → dispatch Worker - -**Benefits:** -- Supports subdomains + custom vanity domains -- No per-route limits (regular Workers limited to 100 routes) -- Programmatic control -- Works with any DNS proxy settings - -**Setup:** -1. Cloudflare for SaaS custom hostnames -2. Fallback origin (dummy `A 192.0.2.0` if Worker is origin) -3. DNS CNAME to SaaS domain -4. `*/*` route → dispatch Worker -5. Routing logic in dispatch Worker - -```typescript -export default { - async fetch(request: Request, env: Env): Promise { - const hostname = new URL(request.url).hostname; - const hostnameData = await env.ROUTING_KV.get(`hostname:${hostname}`, { type: "json" }); - - if (!hostnameData?.workerName) { - return new Response("Hostname not configured", { status: 404 }); - } - - const userWorker = env.DISPATCHER.get(hostnameData.workerName); - return await userWorker.fetch(request); - }, -}; -``` - -### Subdomain-Only -1. Wildcard DNS: `*.saas.com` → origin -2. Route: `*.saas.com/*` → dispatch Worker -3. Extract subdomain for routing - -### Orange-to-Orange (O2O) Behavior - -When customers use Cloudflare and CNAME to your Workers domain: - -| Scenario | Behavior | Route Pattern | -|----------|----------|---------------| -| Customer not on Cloudflare | Standard routing | `*/*` or `*.domain.com/*` | -| Customer on Cloudflare (proxied CNAME) | Invokes Worker at edge | `*/*` required | -| Customer on Cloudflare (DNS-only CNAME) | Standard routing | Any route works | - -**Recommendation:** Always use `*/*` wildcard for consistent O2O behavior. - -### Custom Metadata Routing - -For Cloudflare for SaaS: Store worker name in custom hostname `custom_metadata`, retrieve in dispatch worker to route requests. Requires custom hostnames as subdomains of your domain. - -## Observability - -### Logpush -- Enable on dispatch Worker → captures all user Worker logs -- Filter by `Outcome` or `Script Name` - -### Tail Workers -- Real-time logs with custom formatting -- Receives HTTP status, `console.log()`, exceptions, diagnostics - -### Analytics Engine -```typescript -// Track violations -env.ANALYTICS.writeDataPoint({ - indexes: [customerName], - blobs: ["cpu_limit_exceeded"], -}); -``` - -### GraphQL -```graphql -query { - viewer { - accounts(filter: {accountTag: $accountId}) { - workersInvocationsAdaptive(filter: {dispatchNamespaceName: "production"}) { - sum { requests errors cpuTime } - } - } - } -} -``` - -## Use Case Implementations - -### AI Code Execution -```typescript -async function deployGeneratedCode(name: string, code: string) { - const file = new File([code], `${name}.mjs`, { type: "application/javascript+module" }); - await client.workersForPlatforms.dispatch.namespaces.scripts.update("production", name, { - account_id: accountId, - metadata: { main_module: `${name}.mjs`, tags: [name, "ai-generated"] }, - files: [file], - }); -} - -// Short limits for untrusted code -const userWorker = env.DISPATCHER.get(sessionId, {}, { limits: { cpuMs: 5, subRequests: 3 } }); -``` - -**VibeSDK:** For AI-powered code generation + deployment platforms, see [VibeSDK](https://github.com/cloudflare/vibesdk) - handles AI generation, sandbox execution, live preview, and deployment. - -Reference: [AI Vibe Coding Platform Architecture](https://developers.cloudflare.com/reference-architecture/diagrams/ai/ai-vibe-coding-platform/) - -### Edge Functions Platform -```typescript -// Route: /customer-id/function-name -const [customerId, functionName] = new URL(request.url).pathname.split("/").filter(Boolean); -const workerName = `${customerId}-${functionName}`; -const userWorker = env.DISPATCHER.get(workerName); -``` - -### Website Builder -- Deploy static assets + Worker code -- See [api.md](./api.md#static-assets) for full implementation -- Salt hashes for asset isolation - -## Best Practices - -### Architecture -- One namespace per environment (production, staging) -- Platform logic in dispatch Worker (auth, rate limiting, validation) -- Isolation automatic (no shared cache, untrusted mode) - -### Routing -- Use `*/*` wildcard routes -- Store mappings in KV -- Handle missing Workers gracefully - -### Limits & Security -- Set custom limits by plan -- Track violations with Analytics Engine -- Use outbound Workers for egress control -- Sanitize responses - -### Tags -- Tag all Workers: customer ID, plan, environment -- Enable bulk operations -- Filter efficiently - -See [README.md](./README.md), [configuration.md](./configuration.md), [api.md](./api.md), [gotchas.md](./gotchas.md) diff --git a/external/develop/devops/cloudflare/references/workers-playground/README.md b/external/develop/devops/cloudflare/references/workers-playground/README.md deleted file mode 100644 index 56cb708..0000000 --- a/external/develop/devops/cloudflare/references/workers-playground/README.md +++ /dev/null @@ -1,127 +0,0 @@ -# Cloudflare Workers Playground Skill Reference - -## Overview - -Cloudflare Workers Playground is a browser-based sandbox for instantly experimenting with, testing, and deploying Cloudflare Workers without authentication or setup. This skill provides patterns, APIs, and best practices specifically for Workers Playground development. - -**URL:** [workers.cloudflare.com/playground](https://workers.cloudflare.com/playground) - -## ⚠️ Playground Constraints - -**Playground is NOT production-equivalent:** -- ✅ Real Workers runtime, instant testing, shareable URLs -- ❌ No TypeScript (JavaScript only) -- ❌ No bindings (KV, D1, R2, Durable Objects) -- ❌ No environment variables or secrets -- ❌ ES modules only (no Service Worker format) -- ⚠️ Safari broken (use Chrome/Firefox) - -**For production:** Use `wrangler` CLI. Playground is for rapid prototyping. - -## Quick Start - -Minimal Worker: - -```javascript -export default { - async fetch(request, env, ctx) { - return new Response('Hello World'); - } -}; -``` - -JSON API: - -```javascript -export default { - async fetch(request, env, ctx) { - const data = { message: 'Hello', timestamp: Date.now() }; - return Response.json(data); - } -}; -``` - -Proxy with modification: - -```javascript -export default { - async fetch(request, env, ctx) { - const response = await fetch('https://example.com'); - const modified = new Response(response.body, response); - modified.headers.set('X-Custom-Header', 'added-by-worker'); - return modified; - } -}; -``` - -Import from CDN: - -```javascript -import { Hono } from 'https://esm.sh/hono@3'; - -export default { - async fetch(request) { - const app = new Hono(); - app.get('/', (c) => c.text('Hello Hono!')); - return app.fetch(request); - } -}; -``` - -## Reading Order - -1. **[configuration.md](configuration.md)** - Start here: playground setup, constraints, deployment -2. **[api.md](api.md)** - Core APIs: Request, Response, ExecutionContext, fetch, Cache -3. **[patterns.md](patterns.md)** - Common use cases: routing, proxying, A/B testing, multi-module code -4. **[gotchas.md](gotchas.md)** - Troubleshooting: errors, browser issues, limits, best practices - -## In This Reference - -- **[configuration.md](configuration.md)** - Setup, deployment, configuration -- **[api.md](api.md)** - API endpoints, methods, interfaces -- **[patterns.md](patterns.md)** - Common patterns, use cases, examples -- **[gotchas.md](gotchas.md)** - Troubleshooting, best practices, limitations - -## Key Features - -**No Setup Required:** -- Open URL and start coding -- No CLI, no account, no config files -- Code executes in real Cloudflare Workers runtime - -**Instant Preview:** -- Live preview pane with browser tab or HTTP tester -- Auto-reload on code changes -- DevTools integration (right-click → Inspect) - -**Share & Deploy:** -- Copy Link generates permanent shareable URL -- Deploy button publishes to production in ~30 seconds -- Get `*.workers.dev` subdomain immediately - -## Common Use Cases - -- **API development:** Test endpoints before wrangler setup -- **Learning Workers:** Experiment with APIs without local environment -- **Prototyping:** Quick POCs for edge logic -- **Sharing examples:** Generate shareable links for bug reports or demos -- **Framework testing:** Import from CDN (Hono, itty-router, etc.) - -## Limitations vs Production - -| Feature | Playground | Production (wrangler) | -|---------|------------|----------------------| -| Language | JavaScript only | JS + TypeScript | -| Bindings | None | KV, D1, R2, DO, AI, etc. | -| Environment vars | None | Full support | -| Module format | ES only | ES + Service Worker | -| CPU time | 10ms (Free plan) | 10ms Free / 30s default, 5min max Paid | -| Custom domains | No | Yes | -| Analytics | No | Yes | - -## See Also - -- [Cloudflare Workers Docs](https://developers.cloudflare.com/workers/) -- [Workers Examples](https://developers.cloudflare.com/workers/examples/) -- [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/) -- [Workers API Reference](https://developers.cloudflare.com/workers/runtime-apis/) diff --git a/external/develop/devops/cloudflare/references/workers-playground/api.md b/external/develop/devops/cloudflare/references/workers-playground/api.md deleted file mode 100644 index 0d7dd14..0000000 --- a/external/develop/devops/cloudflare/references/workers-playground/api.md +++ /dev/null @@ -1,101 +0,0 @@ -# Workers Playground API - -## Handler - -```javascript -export default { - async fetch(request, env, ctx) { - // request: Request, env: {} (empty in playground), ctx: ExecutionContext - return new Response('Hello'); - } -}; -``` - -## Request - -```javascript -const method = request.method; // "GET", "POST" -const url = new URL(request.url); // Parse URL -const headers = request.headers; // Headers object -const body = await request.json(); // Read body (consumes stream) -const clone = request.clone(); // Clone before reading body - -// Query params -url.searchParams.get('page'); // Single value -url.searchParams.getAll('tag'); // Array - -// Cloudflare metadata -request.cf.country; // "US" -request.cf.colo; // "SFO" -``` - -## Response - -```javascript -// Text -return new Response('Hello', { status: 200 }); - -// JSON -return Response.json({ data }, { status: 200, headers: {...} }); - -// Redirect -return Response.redirect('/new-path', 301); - -// Modify existing -const modified = new Response(response.body, response); -modified.headers.set('X-Custom', 'value'); -``` - -## ExecutionContext - -```javascript -// Background work (after response sent) -ctx.waitUntil(fetch('https://logs.example.com', { method: 'POST', body: '...' })); -return new Response('OK'); // Returns immediately -``` - -## Fetch - -```javascript -const response = await fetch('https://api.example.com'); -const data = await response.json(); - -// With options -await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: 'Alice' }) -}); -``` - -## Cache - -```javascript -const cache = caches.default; - -// Check cache -let response = await cache.match(request); -if (!response) { - response = await fetch(origin); - await cache.put(request, response.clone()); // Clone before put! -} -return response; -``` - -## Crypto - -```javascript -crypto.randomUUID(); // UUID v4 -crypto.getRandomValues(new Uint8Array(16)); - -// SHA-256 hash -const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(data)); -``` - -## Limits (Playground = Free Plan) - -| Resource | Limit | -|----------|-------| -| CPU time | 10ms (Free plan; Paid: 30s default, 5min max) | -| Subrequests | 50 | -| Memory | 128 MB | diff --git a/external/develop/devops/cloudflare/references/workers-playground/configuration.md b/external/develop/devops/cloudflare/references/workers-playground/configuration.md deleted file mode 100644 index 427d53e..0000000 --- a/external/develop/devops/cloudflare/references/workers-playground/configuration.md +++ /dev/null @@ -1,163 +0,0 @@ -# Configuration - -## Getting Started - -Navigate to [workers.cloudflare.com/playground](https://workers.cloudflare.com/playground) - -- **No account required** for testing -- **No CLI or local setup** needed -- Code executes in real Cloudflare Workers runtime -- Share code via URL (never expires) - -## Playground Constraints - -⚠️ **Important Limitations** - -| Constraint | Playground | Production Workers | -|------------|------------|-------------------| -| **Module Format** | ES modules only | ES modules or Service Worker | -| **TypeScript** | Not supported (JS only) | Supported via build step | -| **Bindings** | Not available | KV, D1, R2, Durable Objects, etc. | -| **wrangler.toml** | Not used | Required for config | -| **Environment Variables** | Not available | Full support | -| **Secrets** | Not available | Full support | -| **Custom Domains** | Not available | Full support | - -**Playground is for rapid prototyping only.** For production apps, use `wrangler` CLI. - -## Code Editor - -### Syntax Requirements - -Must export default object with `fetch` handler: - -```javascript -export default { - async fetch(request, env, ctx) { - return new Response('Hello World'); - } -}; -``` - -**Key Points:** -- Must use ES modules (`export default`) -- `fetch` method receives `(request, env, ctx)` -- Must return `Response` object -- TypeScript not supported (use plain JavaScript) - -### Multi-Module Code - -Import from external URLs or inline modules: - -```javascript -// Import from CDN -import { Hono } from 'https://esm.sh/hono@3'; - -// Or paste library code and import relatively -// (See patterns.md for multi-module examples) - -export default { - async fetch(request) { - const app = new Hono(); - app.get('/', (c) => c.text('Hello')); - return app.fetch(request); - } -}; -``` - -## Preview Panel - -### Browser Tab - -Default interactive preview with address bar: -- Enter custom URL paths -- Automatic reload on code changes -- DevTools available (right-click → Inspect) - -### HTTP Test Panel - -Switch to **HTTP** tab for raw HTTP testing: -- Change HTTP method (GET, POST, PUT, DELETE, PATCH, etc.) -- Add/edit request headers -- Modify request body (JSON, form data, text) -- View response headers and body -- Test different content types - -Example HTTP test: -``` -Method: POST -URL: /api/users -Headers: - Content-Type: application/json - Authorization: Bearer token123 -Body: -{ - "name": "Alice", - "email": "alice@example.com" -} -``` - -## Sharing Code - -**Copy Link** button generates shareable URL: -- Code embedded in URL fragment -- Links never expire -- No account required -- Can be bookmarked for later - -Example: `https://workers.cloudflare.com/playground#abc123...` - -## Deploying from Playground - -Click **Deploy** button to move code to production: - -1. **Log in** to Cloudflare account (creates free account if needed) -2. **Review** Worker name and code -3. **Deploy** to global network (takes ~30 seconds) -4. **Get URL**: Deployed to `.workers.dev` subdomain -5. **Manage** from dashboard: add bindings, custom domains, analytics - -**After deploy:** -- Code runs on Cloudflare's global network (300+ cities) -- Can add KV, D1, R2, Durable Objects bindings -- Configure custom domains and routes -- View analytics and logs -- Set environment variables and secrets - -**Note:** Deployed Workers are production-ready but start on Free plan (100k requests/day). - -## Browser Compatibility - -| Browser | Status | Notes | -|---------|--------|-------| -| Chrome/Edge | ✅ Full support | Recommended | -| Firefox | ✅ Full support | Works well | -| Safari | ⚠️ Broken | Preview fails with "PreviewRequestFailed" | - -**Safari users:** Use Chrome, Firefox, or Edge for Workers Playground. - -## DevTools Integration - -1. **Open preview** in browser tab -2. **Right-click** → Inspect Element -3. **Console tab** shows Worker logs: - - `console.log()` output - - Uncaught errors - - Network requests (subrequests) - -**Note:** DevTools show client-side console, not Worker execution logs. For production logging, use Logpush or Tail Workers. - -## Limits in Playground - -Same as production Free plan: - -| Resource | Limit | Notes | -|----------|-------|-------| -| CPU time | 10ms | Per request | -| Memory | 128 MB | Per request | -| Script size | 1 MB | After compression | -| Subrequests | 50 | Outbound fetch calls | -| Request size | 100 MB | Incoming | -| Response size | Unlimited | Outgoing (streamed) | - -**Exceeding CPU time** throws error immediately. Optimize hot paths or upgrade to Paid plan (30s default, 5min max CPU). diff --git a/external/develop/devops/cloudflare/references/workers-playground/gotchas.md b/external/develop/devops/cloudflare/references/workers-playground/gotchas.md deleted file mode 100644 index 9f5cb93..0000000 --- a/external/develop/devops/cloudflare/references/workers-playground/gotchas.md +++ /dev/null @@ -1,88 +0,0 @@ -# Workers Playground Gotchas - -## Platform Limitations - -| Limitation | Impact | Workaround | -|------------|--------|------------| -| Safari broken | Preview fails | Use Chrome/Firefox/Edge | -| TypeScript unsupported | TS syntax errors | Write plain JS or use JSDoc | -| No bindings | `env` always `{}` | Mock data or use external APIs | -| No env vars | Can't access secrets | Hardcode for testing | - -## Common Runtime Errors - -### "Response body already read" - -```javascript -// ❌ Body consumed twice -const body = await request.text(); -await fetch(url, { body: request.body }); // Error! - -// ✅ Clone first -const clone = request.clone(); -const body = await request.text(); -await fetch(url, { body: clone.body }); -``` - -### "Worker exceeded CPU time" - -**Limit:** 10ms (free), 30s default / 5min max (paid) - -```javascript -// ✅ Move slow work to background -ctx.waitUntil(fetch('https://analytics.example.com', {...})); -return new Response('OK'); // Return immediately -``` - -### "Too many subrequests" - -**Limit:** 50 (free), 1000 (paid) - -```javascript -// ❌ 100 individual fetches -// ✅ Batch into single API call -await fetch('https://api.example.com/batch', { - body: JSON.stringify({ ids: [...] }) -}); -``` - -## Best Practices - -```javascript -// Clone before caching -await cache.put(request, response.clone()); -return response; - -// Validate input early -if (request.method !== 'POST') return new Response('', { status: 405 }); - -// Handle errors -try { ... } catch (e) { - return Response.json({ error: e.message }, { status: 500 }); -} -``` - -## Limits - -| Resource | Free | Paid | -|----------|------|------| -| CPU time | 10ms | 30s (default), 5min (max) | -| Memory | 128 MB | 128 MB | -| Subrequests | 50 | 10,000 | - -## Browser Support - -| Browser | Status | -|---------|--------| -| Chrome | ✅ Recommended | -| Firefox | ✅ Works | -| Edge | ✅ Works | -| Safari | ❌ Broken | - -## Debugging - -```javascript -console.log('URL:', request.url); // View in browser DevTools Console -``` - -**Note:** `console.log` works in playground. For production, use Logpush or Tail Workers. diff --git a/external/develop/devops/cloudflare/references/workers-playground/patterns.md b/external/develop/devops/cloudflare/references/workers-playground/patterns.md deleted file mode 100644 index 4af891c..0000000 --- a/external/develop/devops/cloudflare/references/workers-playground/patterns.md +++ /dev/null @@ -1,132 +0,0 @@ -# Workers Playground Patterns - -## JSON API - -```javascript -export default { - async fetch(request) { - const url = new URL(request.url); - if (url.pathname === '/api/hello') return Response.json({ message: 'Hello' }); - if (url.pathname === '/api/echo' && request.method === 'POST') { - return Response.json({ received: await request.json() }); - } - return Response.json({ error: 'Not found' }, { status: 404 }); - } -}; -``` - -## Router Pattern - -```javascript -const routes = { - '/': () => new Response('Home'), - '/api/users': () => Response.json([{ id: 1, name: 'Alice' }]) -}; - -export default { - async fetch(request) { - const handler = routes[new URL(request.url).pathname]; - return handler ? handler() : new Response('Not Found', { status: 404 }); - } -}; -``` - -## Proxy Pattern - -```javascript -export default { - async fetch(request) { - const url = new URL(request.url); - url.hostname = 'api.example.com'; - return fetch(url.toString(), { - method: request.method, headers: request.headers, body: request.body - }); - } -}; -``` - -## CORS Handling - -```javascript -export default { - async fetch(request) { - if (request.method === 'OPTIONS') { - return new Response(null, { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE', - 'Access-Control-Allow-Headers': 'Content-Type, Authorization' - } - }); - } - const response = await fetch('https://api.example.com', request); - const modified = new Response(response.body, response); - modified.headers.set('Access-Control-Allow-Origin', '*'); - return modified; - } -}; -``` - -## Caching - -```javascript -export default { - async fetch(request) { - if (request.method !== 'GET') return fetch(request); - const cache = caches.default; - let response = await cache.match(request); - if (!response) { - response = await fetch('https://api.example.com'); - if (response.status === 200) await cache.put(request, response.clone()); - } - return response; - } -}; -``` - -## Hono Framework - -```javascript -import { Hono } from 'https://esm.sh/hono@3'; -const app = new Hono(); -app.get('/', (c) => c.text('Hello')); -app.get('/api/users/:id', (c) => c.json({ id: c.req.param('id') })); -app.notFound((c) => c.json({ error: 'Not found' }, 404)); -export default app; -``` - -## Authentication - -```javascript -export default { - async fetch(request) { - const auth = request.headers.get('Authorization'); - if (!auth?.startsWith('Bearer ')) { - return Response.json({ error: 'Unauthorized' }, { status: 401 }); - } - const token = auth.substring(7); - if (token !== 'secret-token') { - return Response.json({ error: 'Invalid token' }, { status: 403 }); - } - return Response.json({ message: 'Authenticated' }); - } -}; -``` - -## Error Handling - -```javascript -export default { - async fetch(request) { - try { - const response = await fetch('https://api.example.com'); - if (!response.ok) throw new Error(`API returned ${response.status}`); - return response; - } catch (error) { - return Response.json({ error: error.message }, { status: 500 }); - } - } -}; -``` - -**Note:** In-memory state (Maps, variables) resets on Worker cold start. Use Durable Objects or KV for persistence. diff --git a/external/develop/devops/cloudflare/references/workers-vpc/README.md b/external/develop/devops/cloudflare/references/workers-vpc/README.md deleted file mode 100644 index 9748c86..0000000 --- a/external/develop/devops/cloudflare/references/workers-vpc/README.md +++ /dev/null @@ -1,127 +0,0 @@ -# Workers VPC Connectivity - -Connect Cloudflare Workers to private networks and internal infrastructure using TCP Sockets. - -## Overview - -Workers VPC connectivity enables outbound TCP connections from Workers to private resources in AWS, Azure, GCP, on-premises datacenters, or any private network. This is achieved through the **TCP Sockets API** (`cloudflare:sockets`), which provides low-level network access for custom protocols and services. - -**Key capabilities:** -- Direct TCP connections to private IPs and hostnames -- TLS/StartTLS support for encrypted connections -- Integration with Cloudflare Tunnel for secure private network access -- Full control over wire protocols (database protocols, SSH, MQTT, custom TCP) - -**Note:** This reference documents the TCP Sockets API. For the newer Workers VPC Services product (HTTP-only service bindings with built-in SSRF protection), refer to separate documentation when available. VPC Services is currently in beta (2025+). - -## Quick Decision: Which Technology? - -Need private network connectivity from Workers? - -| Requirement | Use | Why | -|------------|-----|-----| -| HTTP/HTTPS APIs in private network | VPC Services (beta, separate docs) | SSRF-safe, declarative bindings | -| PostgreSQL/MySQL databases | [Hyperdrive](../hyperdrive/) | Connection pooling, caching, optimized | -| Custom TCP protocols (SSH, MQTT, proprietary) | **TCP Sockets (this doc)** | Full protocol control | -| Simple HTTP with lowest latency | TCP Sockets + [Smart Placement](../smart-placement/) | Manual optimization | -| Expose on-prem to internet (inbound) | [Cloudflare Tunnel](../tunnel/) | Not Worker-specific | - -## When to Use TCP Sockets - -**Use TCP Sockets when you need:** -- ✅ Direct control over wire protocols (e.g., Postgres wire protocol, SSH, Redis RESP) -- ✅ Non-HTTP protocols (MQTT, SMTP, custom binary protocols) -- ✅ StartTLS or custom TLS negotiation -- ✅ Streaming binary data over TCP - -**Don't use TCP Sockets when:** -- ❌ You just need HTTP/HTTPS (use `fetch()` or VPC Services) -- ❌ You need PostgreSQL/MySQL (use Hyperdrive for pooling) -- ❌ You need WebSocket (use native Workers WebSocket) - -## Quick Start - -```typescript -import { connect } from 'cloudflare:sockets'; - -export default { - async fetch(req: Request): Promise { - // Connect to private service - const socket = connect( - { hostname: "db.internal.company.net", port: 5432 }, - { secureTransport: "on" } - ); - - try { - await socket.opened; // Wait for connection - - const writer = socket.writable.getWriter(); - await writer.write(new TextEncoder().encode("QUERY\r\n")); - await writer.close(); - - const reader = socket.readable.getReader(); - const { value } = await reader.read(); - - return new Response(value); - } finally { - await socket.close(); - } - } -}; -``` - -## Architecture Pattern: Workers + Tunnel - -Most private network connectivity combines TCP Sockets with Cloudflare Tunnel: - -``` -┌─────────┐ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ -│ Worker │────▶│ TCP Socket │────▶│ Tunnel │────▶│ Private │ -│ │ │ (this API) │ │ (cloudflared)│ │ Network │ -└─────────┘ └─────────────┘ └──────────────┘ └─────────────┘ -``` - -1. Worker opens TCP socket to Tunnel hostname -2. Tunnel endpoint routes to private IP -3. Response flows back through Tunnel to Worker - -See [configuration.md](./configuration.md) for Tunnel setup details. - -## Reading Order - -1. **Start here (README.md)** - Overview and decision guide -2. **[api.md](./api.md)** - Socket interface, types, methods -3. **[configuration.md](./configuration.md)** - Wrangler setup, Tunnel integration -4. **[patterns.md](./patterns.md)** - Real-world examples (databases, protocols, error handling) -5. **[gotchas.md](./gotchas.md)** - Limits, blocked ports, common errors - -## Key Limits - -| Limit | Value | -|-------|-------| -| Max concurrent sockets per request | 6 | -| Blocked destinations | Cloudflare IPs, localhost, port 25 | -| Scope requirement | Must create in handler (not global) | - -See [gotchas.md](./gotchas.md) for complete limits and troubleshooting. - -## Best Practices - -1. **Always close sockets** - Use try/finally blocks -2. **Validate destinations** - Prevent SSRF by allowlisting hosts -3. **Use Hyperdrive for databases** - Better performance than raw TCP -4. **Prefer fetch() for HTTP** - Only use TCP when necessary -5. **Combine with Smart Placement** - Reduce latency to private networks - -## Related Technologies - -- **[Hyperdrive](../hyperdrive/)** - PostgreSQL/MySQL with connection pooling -- **[Cloudflare Tunnel](../tunnel/)** - Secure private network access -- **[Smart Placement](../smart-placement/)** - Auto-locate Workers near backends -- **VPC Services (beta)** - HTTP-only service bindings with SSRF protection (separate docs) - -## Reference - -- [TCP Sockets API Documentation](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/) -- [Connect to databases guide](https://developers.cloudflare.com/workers/tutorials/postgres/) -- [Cloudflare Tunnel setup](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) diff --git a/external/develop/devops/cloudflare/references/workers-vpc/api.md b/external/develop/devops/cloudflare/references/workers-vpc/api.md deleted file mode 100644 index 987fb2e..0000000 --- a/external/develop/devops/cloudflare/references/workers-vpc/api.md +++ /dev/null @@ -1,202 +0,0 @@ -# TCP Sockets API Reference - -Complete API reference for the Cloudflare Workers TCP Sockets API (`cloudflare:sockets`). - -## Core Function: `connect()` - -```typescript -function connect( - address: SocketAddress, - options?: SocketOptions -): Socket -``` - -Creates an outbound TCP connection to the specified address. - -### Parameters - -#### `SocketAddress` - -```typescript -interface SocketAddress { - hostname: string; // DNS hostname or IP address - port: number; // TCP port (1-65535, excluding blocked ports) -} -``` - -| Field | Type | Description | Example | -|-------|------|-------------|---------| -| `hostname` | `string` | Target hostname or IP | `"db.internal.net"`, `"10.0.1.50"` | -| `port` | `number` | TCP port number | `5432`, `443`, `22` | - -DNS names are resolved at connection time. IPv4, IPv6, and private IPs (10.x, 172.16.x, 192.168.x) supported. - -#### `SocketOptions` - -```typescript -interface SocketOptions { - secureTransport?: "off" | "on" | "starttls"; - allowHalfOpen?: boolean; -} -``` - -| Field | Type | Default | Description | -|-------|------|---------|-------------| -| `secureTransport` | `"off" \| "on" \| "starttls"` | `"off"` | TLS mode | -| `allowHalfOpen` | `boolean` | `false` | Allow half-closed connections | - -**`secureTransport` modes:** - -| Mode | Behavior | Use Case | -|------|----------|----------| -| `"off"` | Plain TCP, no encryption | Testing, internal trusted networks | -| `"on"` | Immediate TLS handshake | HTTPS, secure databases, SSH | -| `"starttls"` | Start plain, upgrade later with `startTls()` | Postgres, SMTP, IMAP | - -**`allowHalfOpen`:** When `false` (default), closing read stream auto-closes write stream. When `true`, streams are independent. - -### Returns - -A `Socket` object with readable/writable streams. - -## Socket Interface - -```typescript -interface Socket { - // Streams - readable: ReadableStream; - writable: WritableStream; - - // Connection state - opened: Promise; - closed: Promise; - - // Methods - close(): Promise; - startTls(): Socket; -} -``` - -### Properties - -#### `readable: ReadableStream` - -Stream for reading data from the socket. Use `getReader()` to consume data. - -```typescript -const reader = socket.readable.getReader(); -const { done, value } = await reader.read(); // Read one chunk -``` - -#### `writable: WritableStream` - -Stream for writing data to the socket. Use `getWriter()` to send data. - -```typescript -const writer = socket.writable.getWriter(); -await writer.write(new TextEncoder().encode("HELLO\r\n")); -await writer.close(); -``` - -#### `opened: Promise` - -Promise that resolves when connection succeeds, rejects on failure. - -```typescript -interface SocketInfo { - remoteAddress?: string; // May be undefined - localAddress?: string; // May be undefined -} - -try { - const info = await socket.opened; -} catch (error) { - // Connection failed -} -``` - -#### `closed: Promise` - -Promise that resolves when socket is fully closed (both directions). - -### Methods - -#### `close(): Promise` - -Closes the socket gracefully, waiting for pending writes to complete. - -```typescript -const socket = connect({ hostname: "api.internal", port: 443 }); -try { - // Use socket -} finally { - await socket.close(); // Always call in finally block -} -``` - -#### `startTls(): Socket` - -Upgrades connection to TLS. Only available when `secureTransport: "starttls"` was specified. - -```typescript -const socket = connect( - { hostname: "db.internal", port: 5432 }, - { secureTransport: "starttls" } -); - -// Send protocol-specific StartTLS command -const writer = socket.writable.getWriter(); -await writer.write(new TextEncoder().encode("STARTTLS\r\n")); - -// Upgrade to TLS - use returned socket, not original -const secureSocket = socket.startTls(); -const secureWriter = secureSocket.writable.getWriter(); -``` - -## Complete Example - -```typescript -import { connect } from 'cloudflare:sockets'; - -export default { - async fetch(req: Request): Promise { - const socket = connect({ hostname: "echo.example.com", port: 7 }, { secureTransport: "on" }); - - try { - await socket.opened; - - const writer = socket.writable.getWriter(); - await writer.write(new TextEncoder().encode("Hello, TCP!\n")); - await writer.close(); - - const reader = socket.readable.getReader(); - const { value } = await reader.read(); - - return new Response(value); - } finally { - await socket.close(); - } - } -}; -``` - -See [patterns.md](./patterns.md) for multi-chunk reading, error handling, and protocol implementations. - -## Quick Reference - -| Task | Code | -|------|------| -| Import | `import { connect } from 'cloudflare:sockets';` | -| Connect | `connect({ hostname: "host", port: 443 })` | -| With TLS | `connect(addr, { secureTransport: "on" })` | -| StartTLS | `socket.startTls()` after handshake | -| Write | `await writer.write(data); await writer.close();` | -| Read | `const { value } = await reader.read();` | -| Error handling | `try { await socket.opened; } catch { }` | -| Always close | `try { } finally { await socket.close(); }` | - -## See Also - -- [patterns.md](./patterns.md) - Real-world protocol implementations -- [configuration.md](./configuration.md) - Wrangler setup and environment variables -- [gotchas.md](./gotchas.md) - Limits and error handling diff --git a/external/develop/devops/cloudflare/references/workers-vpc/configuration.md b/external/develop/devops/cloudflare/references/workers-vpc/configuration.md deleted file mode 100644 index efd2d35..0000000 --- a/external/develop/devops/cloudflare/references/workers-vpc/configuration.md +++ /dev/null @@ -1,147 +0,0 @@ -# Configuration - -Setup and configuration for TCP Sockets in Cloudflare Workers. - -## Wrangler Configuration - -### Basic Setup - -TCP Sockets are available by default in Workers runtime. No special configuration required in `wrangler.jsonc`: - -```jsonc -{ - "name": "private-network-worker", - "main": "src/index.ts", - "compatibility_date": "2025-01-01" -} -``` - -### Environment Variables - -Store connection details as env vars: - -```jsonc -{ - "vars": { "DB_HOST": "10.0.1.50", "DB_PORT": "5432" } -} -``` - -```typescript -interface Env { DB_HOST: string; DB_PORT: string; } - -export default { - async fetch(req: Request, env: Env): Promise { - const socket = connect({ hostname: env.DB_HOST, port: parseInt(env.DB_PORT) }); - } -}; -``` - -### Per-Environment Configuration - -```jsonc -{ - "vars": { "DB_HOST": "localhost" }, - "env": { - "staging": { "vars": { "DB_HOST": "staging-db.internal.net" } }, - "production": { "vars": { "DB_HOST": "prod-db.internal.net" } } - } -} -``` - -Deploy: `wrangler deploy --env staging` or `wrangler deploy --env production` - -## Integration with Cloudflare Tunnel - -To connect Workers to private networks, combine TCP Sockets with Cloudflare Tunnel: - -``` -Worker (TCP Socket) → Tunnel hostname → cloudflared → Private Network -``` - -### Quick Setup - -1. **Install cloudflared** on a server inside your private network -2. **Create tunnel**: `cloudflared tunnel create my-private-network` -3. **Configure routing** in `config.yml`: - -```yaml -tunnel: -credentials-file: /path/to/.json -ingress: - - hostname: db.internal.example.com - service: tcp://10.0.1.50:5432 - - service: http_status:404 # Required catch-all -``` - -4. **Run tunnel**: `cloudflared tunnel run my-private-network` -5. **Connect from Worker**: - -```typescript -const socket = connect( - { hostname: "db.internal.example.com", port: 5432 }, // Tunnel hostname - { secureTransport: "on" } -); -``` - -For detailed Tunnel setup, see [Tunnel configuration reference](../tunnel/configuration.md). - -## Smart Placement Integration - -Reduce latency by auto-placing Workers near backends: - -```jsonc -{ "placement": { "mode": "smart" } } -``` - -Workers automatically relocate closer to TCP socket destinations after observing connection latency. See [Smart Placement reference](../smart-placement/). - -## Secrets Management - -Store sensitive credentials as secrets (not in wrangler.jsonc): - -```bash -wrangler secret put DB_PASSWORD # Enter value when prompted -``` - -Access in Worker via `env.DB_PASSWORD`. Use in protocol handshake or authentication. - -## Local Development - -Test with `wrangler dev`. Note: Local mode may not access private networks. Use public endpoints or mock servers for development: - -```typescript -const config = process.env.NODE_ENV === 'dev' - ? { hostname: 'localhost', port: 5432 } // Mock - : { hostname: 'db.internal.example.com', port: 5432 }; // Production -``` - -## Connection String Patterns - -Parse connection strings to extract host and port: - -```typescript -function parseConnectionString(connStr: string): SocketAddress { - const url = new URL(connStr); // e.g., "postgres://10.0.1.50:5432/mydb" - return { hostname: url.hostname, port: parseInt(url.port) || 5432 }; -} -``` - -## Hyperdrive Integration - -For PostgreSQL/MySQL, prefer Hyperdrive over raw TCP sockets (includes connection pooling): - -```jsonc -{ "hyperdrive": [{ "binding": "DB", "id": "" }] } -``` - -See [Hyperdrive reference](../hyperdrive/) for complete setup. - -## Compatibility - -TCP Sockets available in all modern Workers. Use current date: `"compatibility_date": "2025-01-01"`. No special flags required. - -## Related Configuration - -- **[Tunnel Configuration](../tunnel/configuration.md)** - Detailed cloudflared setup -- **[Smart Placement](../smart-placement/configuration.md)** - Placement mode options -- **[Hyperdrive](../hyperdrive/configuration.md)** - Database connection pooling setup diff --git a/external/develop/devops/cloudflare/references/workers-vpc/gotchas.md b/external/develop/devops/cloudflare/references/workers-vpc/gotchas.md deleted file mode 100644 index d14faae..0000000 --- a/external/develop/devops/cloudflare/references/workers-vpc/gotchas.md +++ /dev/null @@ -1,167 +0,0 @@ -# Gotchas and Troubleshooting - -Common pitfalls, limitations, and solutions for TCP Sockets in Cloudflare Workers. - -## Platform Limits - -### Connection Limits - -| Limit | Value | -|-------|-------| -| Max concurrent sockets per request | 6 (hard limit) | -| Socket lifetime | Request duration | -| Connection timeout | Platform-dependent, no setting | - -**Problem:** Exceeding 6 connections throws error - -**Solution:** Process in batches of 6 - -```typescript -for (let i = 0; i < hosts.length; i += 6) { - const batch = hosts.slice(i, i + 6).map(h => connect({ hostname: h, port: 443 })); - await Promise.all(batch.map(async s => { /* use */ await s.close(); })); -} -``` - -### Blocked Destinations - -Cloudflare IPs (1.1.1.1), localhost (127.0.0.1), port 25 (SMTP), Worker's own URL blocked for security. - -**Solution:** Use public IPs or Tunnel hostnames: `connect({ hostname: "db.internal.company.net", port: 5432 })` - -### Scope Requirements - -**Problem:** Sockets created in global scope fail - -**Cause:** Sockets tied to request lifecycle - -**Solution:** Create inside handler: `export default { async fetch() { const socket = connect(...); } }` - -## Common Errors - -### Error: "proxy request failed" - -**Causes:** Blocked destination (Cloudflare IP, localhost, port 25), DNS failure, network unreachable - -**Solution:** Validate destinations, use Tunnel hostnames, catch errors with try/catch - -### Error: "TCP Loop detected" - -**Cause:** Worker connecting to itself - -**Solution:** Connect to external service, not Worker's own hostname - -### Error: "Port 25 prohibited" - -**Cause:** SMTP port blocked - -**Solution:** Use Email Workers API for email - -### Error: "socket is not open" - -**Cause:** Read/write after close - -**Solution:** Always use try/finally to ensure proper closure order - -### Error: Connection timeout - -**Cause:** No built-in timeout - -**Solution:** Use `Promise.race()`: - -```typescript -const socket = connect(addr, opts); -const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000)); -await Promise.race([socket.opened, timeout]); -``` - -## TLS/SSL Issues - -### StartTLS Timing - -**Problem:** Calling `startTls()` too early - -**Solution:** Send protocol-specific STARTTLS command, wait for server OK, then call `socket.startTls()` - -### Certificate Validation - -**Problem:** Self-signed certs fail - -**Solution:** Use proper certs or Tunnel (handles TLS termination) - -## Performance Issues - -### Not Using Connection Pooling - -**Problem:** New connection overhead per request - -**Solution:** Use [Hyperdrive](../hyperdrive/) for databases (built-in pooling) - -### Not Using Smart Placement - -**Problem:** High latency to backend - -**Solution:** Enable: `{ "placement": { "mode": "smart" } }` in wrangler.jsonc - -### Forgetting to Close Sockets - -**Problem:** Resource leaks - -**Solution:** Always use try/finally: - -```typescript -const socket = connect({ hostname: "api.internal", port: 443 }); -try { - // Use socket -} finally { - await socket.close(); -} -``` - -## Data Handling Issues - -### Assuming Single Read Gets All Data - -**Problem:** Only reading once may miss chunked data - -**Solution:** Loop `reader.read()` until `done === true` (see patterns.md) - -### Text Encoding Issues - -**Problem:** Using wrong encoding - -**Solution:** Specify encoding: `new TextDecoder('iso-8859-1').decode(data)` - -## Security Issues - -### SSRF Vulnerability - -**Problem:** User-controlled destinations allow access to internal services - -**Solution:** Validate against strict allowlist: - -```typescript -const ALLOWED = ['api1.internal.net', 'api2.internal.net']; -const host = new URL(req.url).searchParams.get('host'); -if (!host || !ALLOWED.includes(host)) return new Response('Forbidden', { status: 403 }); -``` - -## When to Use Alternatives - -| Use Case | Alternative | Reason | -|----------|-------------|--------| -| PostgreSQL/MySQL | [Hyperdrive](../hyperdrive/) | Connection pooling, caching | -| HTTP/HTTPS | `fetch()` | Simpler, built-in | -| HTTP with SSRF protection | VPC Services (beta 2025+) | Declarative bindings | - -## Debugging Tips - -1. **Log connection details:** `const info = await socket.opened; console.log(info.remoteAddress);` -2. **Test with public services first:** Use tcpbin.com:4242 echo server -3. **Verify Tunnel:** `cloudflared tunnel info ` and `cloudflared tunnel route ip list` - -## Related - -- [Hyperdrive](../hyperdrive/) - Database connections -- [Smart Placement](../smart-placement/) - Latency optimization -- [Tunnel Troubleshooting](../tunnel/gotchas.md) diff --git a/external/develop/devops/cloudflare/references/workers-vpc/patterns.md b/external/develop/devops/cloudflare/references/workers-vpc/patterns.md deleted file mode 100644 index 392627e..0000000 --- a/external/develop/devops/cloudflare/references/workers-vpc/patterns.md +++ /dev/null @@ -1,209 +0,0 @@ -# Common Patterns - -Real-world patterns and examples for TCP Sockets in Cloudflare Workers. - -```typescript -import { connect } from 'cloudflare:sockets'; -``` - -## Basic Patterns - -### Simple Request-Response - -```typescript -const socket = connect({ hostname: "echo.example.com", port: 7 }, { secureTransport: "on" }); -try { - await socket.opened; - const writer = socket.writable.getWriter(); - await writer.write(new TextEncoder().encode("Hello\n")); - await writer.close(); - - const reader = socket.readable.getReader(); - const { value } = await reader.read(); - return new Response(value); -} finally { - await socket.close(); -} -``` - -### Reading All Data - -```typescript -async function readAll(socket: Socket): Promise { - const reader = socket.readable.getReader(); - const chunks: Uint8Array[] = []; - while (true) { - const { done, value } = await reader.read(); - if (done) break; - chunks.push(value); - } - const total = chunks.reduce((sum, c) => sum + c.length, 0); - const result = new Uint8Array(total); - let offset = 0; - for (const chunk of chunks) { result.set(chunk, offset); offset += chunk.length; } - return result; -} -``` - -### Streaming Response - -```typescript -// Stream socket data directly to HTTP response -const socket = connect({ hostname: "stream.internal", port: 9000 }, { secureTransport: "on" }); -const writer = socket.writable.getWriter(); -await writer.write(new TextEncoder().encode("STREAM\n")); -await writer.close(); -return new Response(socket.readable); -``` - -## Protocol Examples - -### Redis RESP - -```typescript -// Send: *2\r\n$3\r\nGET\r\n$\r\n\r\n -// Recv: $\r\n\r\n or $-1\r\n for null -const socket = connect({ hostname: "redis.internal", port: 6379 }); -const writer = socket.writable.getWriter(); -await writer.write(new TextEncoder().encode(`*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n`)); -``` - -### PostgreSQL - -**Use [Hyperdrive](../hyperdrive/) for production.** Raw Postgres protocol is complex (startup, auth, query messages). - -### MQTT - -```typescript -const socket = connect({ hostname: "mqtt.broker", port: 1883 }); -const writer = socket.writable.getWriter(); -// CONNECT: 0x10 0x00 0x04 "MQTT" 0x04 ... -// PUBLISH: 0x30 -``` - -## Error Handling Patterns - -### Retry with Backoff - -```typescript -async function connectWithRetry(addr: SocketAddress, opts: SocketOptions, maxRetries = 3): Promise { - for (let i = 1; i <= maxRetries; i++) { - try { - const socket = connect(addr, opts); - await socket.opened; - return socket; - } catch (error) { - if (i === maxRetries) throw error; - await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i - 1))); // Exponential backoff - } - } - throw new Error('Unreachable'); -} -``` - -### Timeout - -```typescript -async function connectWithTimeout(addr: SocketAddress, opts: SocketOptions, ms = 5000): Promise { - const socket = connect(addr, opts); - const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), ms)); - await Promise.race([socket.opened, timeout]); - return socket; -} -``` - -### Fallback - -```typescript -async function connectWithFallback(primary: string, fallback: string, port: number): Promise { - try { - const socket = connect({ hostname: primary, port }, { secureTransport: "on" }); - await socket.opened; - return socket; - } catch { - return connect({ hostname: fallback, port }, { secureTransport: "on" }); - } -} -``` - -## Security Patterns - -### Destination Allowlist (Prevent SSRF) - -```typescript -const ALLOWED_HOSTS = ['db.internal.company.net', 'api.internal.company.net', /^10\.0\.1\.\d+$/]; - -function isAllowed(hostname: string): boolean { - return ALLOWED_HOSTS.some(p => p instanceof RegExp ? p.test(hostname) : p === hostname); -} - -export default { - async fetch(req: Request): Promise { - const target = new URL(req.url).searchParams.get('host'); - if (!target || !isAllowed(target)) return new Response('Forbidden', { status: 403 }); - const socket = connect({ hostname: target, port: 443 }); - // Use socket... - } -}; -``` - -### Connection Pooling - -```typescript -class SocketPool { - private pool = new Map(); - - async acquire(hostname: string, port: number): Promise { - const key = `${hostname}:${port}`; - const sockets = this.pool.get(key) || []; - if (sockets.length > 0) return sockets.pop()!; - const socket = connect({ hostname, port }, { secureTransport: "on" }); - await socket.opened; - return socket; - } - - release(hostname: string, port: number, socket: Socket): void { - const key = `${hostname}:${port}`; - const sockets = this.pool.get(key) || []; - if (sockets.length < 3) { sockets.push(socket); this.pool.set(key, sockets); } - else socket.close(); - } -} -``` - -## Multi-Protocol Gateway - -```typescript -interface Protocol { name: string; defaultPort: number; test(host: string, port: number): Promise; } - -const PROTOCOLS: Record = { - redis: { - name: 'redis', - defaultPort: 6379, - async test(host, port) { - const socket = connect({ hostname: host, port }); - try { - const writer = socket.writable.getWriter(); - await writer.write(new TextEncoder().encode('*1\r\n$4\r\nPING\r\n')); - writer.releaseLock(); - const reader = socket.readable.getReader(); - const { value } = await reader.read(); - return new TextDecoder().decode(value || new Uint8Array()); - } finally { await socket.close(); } - } - } -}; - -export default { - async fetch(req: Request): Promise { - const url = new URL(req.url); - const proto = url.pathname.slice(1); // /redis - const host = url.searchParams.get('host'); - if (!host || !PROTOCOLS[proto]) return new Response('Invalid', { status: 400 }); - const result = await PROTOCOLS[proto].test(host, parseInt(url.searchParams.get('port') || '') || PROTOCOLS[proto].defaultPort); - return new Response(result); - } -}; -``` - - diff --git a/external/develop/devops/cloudflare/references/workflows/README.md b/external/develop/devops/cloudflare/references/workflows/README.md index dac84ab..adf61c2 100644 --- a/external/develop/devops/cloudflare/references/workflows/README.md +++ b/external/develop/devops/cloudflare/references/workflows/README.md @@ -4,10 +4,10 @@ Use Workflows for durable, multi-step jobs that must retry, wait, and resume wit Fetch the relevant current documentation before implementing. API shapes, configuration, testing helpers, limits, and examples belong in the docs rather than in this reference. -- **Start a project:** [Build your first Workflow](https://developers.cloudflare.com/workflows/get-started/guide/) covers scaffolding, configuration, deployment, and a first instance. -- **Design durable execution:** [Rules of Workflows](https://developers.cloudflare.com/workflows/build/rules-of-workflows/) covers step boundaries, replay, state, and idempotency. -- **Implement or manage an instance:** [Workers API](https://developers.cloudflare.com/workflows/build/workers-api/) covers steps, instance operations, parameters, and return types. -- **Check capacity and cost:** fetch [limits](https://developers.cloudflare.com/workflows/reference/limits/) and [pricing](https://developers.cloudflare.com/workflows/reference/pricing/) for the target plan. +- **Start a project:** [Build your first Workflow](https://developers.cloudflare.com/workflows/get-started/guide/index.md) covers scaffolding, configuration, deployment, and a first instance. +- **Design durable execution:** [Rules of Workflows](https://developers.cloudflare.com/workflows/build/rules-of-workflows/index.md) covers step boundaries, replay, state, and idempotency. +- **Implement or manage an instance:** [Workers API](https://developers.cloudflare.com/workflows/build/workers-api/index.md) covers steps, instance operations, parameters, and return types. +- **Check capacity and cost:** fetch [limits](https://developers.cloudflare.com/workflows/reference/limits/index.md) and [pricing](https://developers.cloudflare.com/workflows/reference/pricing/index.md) for the target plan. ## In This Reference @@ -18,6 +18,6 @@ Fetch the relevant current documentation before implementing. API shapes, config ## See Also -- [Durable Objects](https://developers.cloudflare.com/durable-objects/) — stateful coordination +- [Durable Objects](https://developers.cloudflare.com/durable-objects/index.md) — stateful coordination - [Queues](../queues/README.md) — asynchronous message delivery -- [Workers](https://developers.cloudflare.com/workers/) — application entry points that trigger instances +- [Workers](https://developers.cloudflare.com/workers/index.md) — application entry points that trigger instances diff --git a/external/develop/devops/cloudflare/references/workflows/api.md b/external/develop/devops/cloudflare/references/workflows/api.md index 79946b6..fb583aa 100644 --- a/external/develop/devops/cloudflare/references/workflows/api.md +++ b/external/develop/devops/cloudflare/references/workflows/api.md @@ -4,13 +4,13 @@ Fetch the documentation for the operation before writing code; use its current s | Task | Documentation | | --- | --- | -| Implement steps; create, batch, inspect, pause, resume, restart, or terminate instances; check parameter and return types | [Workers API](https://developers.cloudflare.com/workflows/build/workers-api/) | -| Access a step's name, occurrence, retry attempt, and resolved configuration | [Step context](https://developers.cloudflare.com/workflows/build/step-context/) | -| Configure retries, backoff, timeouts, non-retryable failures, or relative/absolute sleeps | [Sleeping and retrying](https://developers.cloudflare.com/workflows/build/sleeping-and-retrying/) | -| Pass initial parameters, wait for an external event, or send an event to an instance | [Events and parameters](https://developers.cloudflare.com/workflows/build/events-and-parameters/) | -| Start instances from a Worker or another Workflow, or schedule execution | [Trigger Workflows](https://developers.cloudflare.com/workflows/build/trigger-workflows/) | -| Trigger and manage instances from the command line | [Wrangler commands](https://developers.cloudflare.com/workflows/reference/wrangler-commands/) | -| Manage Workflows over HTTP, including authentication and request bodies | [Workflows REST API](https://developers.cloudflare.com/api/resources/workflows/methods/list/) | +| Implement steps; create, batch, inspect, pause, resume, restart, or terminate instances; check parameter and return types | [Workers API](https://developers.cloudflare.com/workflows/build/workers-api/index.md) | +| Access a step's name, occurrence, retry attempt, and resolved configuration | [Step context](https://developers.cloudflare.com/workflows/build/step-context/index.md) | +| Configure retries, backoff, timeouts, non-retryable failures, or relative/absolute sleeps | [Sleeping and retrying](https://developers.cloudflare.com/workflows/build/sleeping-and-retrying/index.md) | +| Pass initial parameters, wait for an external event, or send an event to an instance | [Events and parameters](https://developers.cloudflare.com/workflows/build/events-and-parameters/index.md) | +| Start instances from a Worker or another Workflow, or schedule execution | [Trigger Workflows](https://developers.cloudflare.com/workflows/build/trigger-workflows/index.md) | +| Trigger and manage instances from the command line | [Wrangler commands](https://developers.cloudflare.com/workflows/reference/wrangler-commands/index.md) | +| Manage Workflows over HTTP, including authentication and request bodies | [Workflows REST API](https://developers.cloudflare.com/api/resources/workflows/methods/list/index.md) | Decide whether input is available at creation or must arrive later as an event. Starting a child instance does not establish that it has completed; choose how the parent will observe completion. Check current instance ID retention and creation semantics before designing duplicate-trigger handling. diff --git a/external/develop/devops/cloudflare/references/workflows/configuration.md b/external/develop/devops/cloudflare/references/workflows/configuration.md index 2beaa06..95786d2 100644 --- a/external/develop/devops/cloudflare/references/workflows/configuration.md +++ b/external/develop/devops/cloudflare/references/workflows/configuration.md @@ -4,14 +4,14 @@ Use the current guides for configuration fields and setup commands; check the pr | Task | Documentation | | --- | --- | -| Scaffold and deploy a Workflow class with its binding | [Build your first Workflow](https://developers.cloudflare.com/workflows/get-started/guide/) | -| Configure one or more Workflows, including a binding to a Workflow in another Worker | [Wrangler Workflows configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#workflows) | -| Configure storage, AI, and other resources used by steps | [Workers bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/) | -| Choose step retry, backoff, timeout, and sleep behavior | [Sleeping and retrying](https://developers.cloudflare.com/workflows/build/sleeping-and-retrying/) | -| Schedule instances or trigger them from another Worker | [Trigger Workflows](https://developers.cloudflare.com/workflows/build/trigger-workflows/) | -| Trigger a Workflow from Pages Functions | [Call Workflows from Pages](https://developers.cloudflare.com/workflows/build/call-workflows-from-pages/) | -| Develop and inspect instances locally | [Local development](https://developers.cloudflare.com/workflows/build/local-development/) | -| Set resource budgets and inspect execution | [Limits](https://developers.cloudflare.com/workflows/reference/limits/) and [metrics and analytics](https://developers.cloudflare.com/workflows/observability/metrics-analytics/) | +| Scaffold and deploy a Workflow class with its binding | [Build your first Workflow](https://developers.cloudflare.com/workflows/get-started/guide/index.md) | +| Configure one or more Workflows, including a binding to a Workflow in another Worker | [Wrangler Workflows configuration](https://developers.cloudflare.com/workers/wrangler/configuration/index.md#workflows) | +| Configure storage, AI, and other resources used by steps | [Workers bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/index.md) | +| Choose step retry, backoff, timeout, and sleep behavior | [Sleeping and retrying](https://developers.cloudflare.com/workflows/build/sleeping-and-retrying/index.md) | +| Schedule instances or trigger them from another Worker | [Trigger Workflows](https://developers.cloudflare.com/workflows/build/trigger-workflows/index.md) | +| Trigger a Workflow from Pages Functions | [Call Workflows from Pages](https://developers.cloudflare.com/workflows/build/call-workflows-from-pages/index.md) | +| Develop and inspect instances locally | [Local development](https://developers.cloudflare.com/workflows/build/local-development/index.md) | +| Set resource budgets and inspect execution | [Limits](https://developers.cloudflare.com/workflows/reference/limits/index.md) and [metrics and analytics](https://developers.cloudflare.com/workflows/observability/metrics-analytics/index.md) | Distinguish the Worker that defines the Workflow from callers that trigger it. For Pages, follow the documented intermediary Worker/service-binding approach. A step's elapsed-time timeout and the Worker's active CPU budget address different failure modes; configure them based on the actual failure. diff --git a/external/develop/devops/cloudflare/references/workflows/gotchas.md b/external/develop/devops/cloudflare/references/workflows/gotchas.md index 00348f2..f71124b 100644 --- a/external/develop/devops/cloudflare/references/workflows/gotchas.md +++ b/external/develop/devops/cloudflare/references/workflows/gotchas.md @@ -4,15 +4,15 @@ Start with the failing instance and step, then fetch the relevant guide before c | Symptom or question | What to check | | --- | --- | -| Step timeout or repeated failure | [Sleeping and retrying](https://developers.cloudflare.com/workflows/build/sleeping-and-retrying/) for per-attempt timeout, retry policy, and non-retryable failures | -| CPU exhaustion despite a short run | [Limits](https://developers.cloudflare.com/workflows/reference/limits/) for active CPU budgets; increasing an elapsed-time timeout does not increase CPU capacity | -| Missing event or event timeout | [Events and parameters](https://developers.cloudflare.com/workflows/build/events-and-parameters/) for instance targeting, event type/payload requirements, and timeout handling | -| State disappears or branches change after resuming | [Rules of Workflows](https://developers.cloudflare.com/workflows/build/rules-of-workflows/) for persisted step returns, deterministic names and conditionals, and awaited operations | +| Step timeout or repeated failure | [Sleeping and retrying](https://developers.cloudflare.com/workflows/build/sleeping-and-retrying/index.md) for per-attempt timeout, retry policy, and non-retryable failures | +| CPU exhaustion despite a short run | [Limits](https://developers.cloudflare.com/workflows/reference/limits/index.md) for active CPU budgets; increasing an elapsed-time timeout does not increase CPU capacity | +| Missing event or event timeout | [Events and parameters](https://developers.cloudflare.com/workflows/build/events-and-parameters/index.md) for instance targeting, event type/payload requirements, and timeout handling | +| State disappears or branches change after resuming | [Rules of Workflows](https://developers.cloudflare.com/workflows/build/rules-of-workflows/index.md) for persisted step returns, deterministic names and conditionals, and awaited operations | | Duplicate charge, write, or notification | Review the destination's idempotency guarantees and [step design](./patterns.md#design-decisions); retries can repeat an external operation even when its previous attempt committed | -| Instance ID collision or unexpected batch result | [Workers API](https://developers.cloudflare.com/workflows/build/workers-api/) for creation semantics, plus [limits](https://developers.cloudflare.com/workflows/reference/limits/) for retention | -| Oversized results, queued instances, or missing historical data | [Limits](https://developers.cloudflare.com/workflows/reference/limits/) for return/event sizes, concurrency, creation rates, and retention; export required long-term results before expiry | -| Local-only failure or failing introspection test | [Local development](https://developers.cloudflare.com/workflows/build/local-development/) and [Workflow test APIs](https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/#workflows) | -| Inspect execution and cost | [Metrics and analytics](https://developers.cloudflare.com/workflows/observability/metrics-analytics/), [Wrangler commands](https://developers.cloudflare.com/workflows/reference/wrangler-commands/), and [pricing](https://developers.cloudflare.com/workflows/reference/pricing/) | +| Instance ID collision or unexpected batch result | [Workers API](https://developers.cloudflare.com/workflows/build/workers-api/index.md) for creation semantics, plus [limits](https://developers.cloudflare.com/workflows/reference/limits/index.md) for retention | +| Oversized results, queued instances, or missing historical data | [Limits](https://developers.cloudflare.com/workflows/reference/limits/index.md) for return/event sizes, concurrency, creation rates, and retention; export required long-term results before expiry | +| Local-only failure or failing introspection test | [Local development](https://developers.cloudflare.com/workflows/build/local-development/index.md) and [Workflow test APIs](https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/index.md#workflows) | +| Inspect execution and cost | [Metrics and analytics](https://developers.cloudflare.com/workflows/observability/metrics-analytics/index.md), [Wrangler commands](https://developers.cloudflare.com/workflows/reference/wrangler-commands/index.md), and [pricing](https://developers.cloudflare.com/workflows/reference/pricing/index.md) | CPU time measures active computation; waiting for network or storage I/O is elapsed time. Event waits, sleeps, and retry delays also have their own documented behavior. Check the current limits page for how these states affect concurrency and step accounting rather than treating every wait as active execution. diff --git a/external/develop/devops/cloudflare/references/workflows/patterns.md b/external/develop/devops/cloudflare/references/workflows/patterns.md index 5c12d8b..6b6851f 100644 --- a/external/develop/devops/cloudflare/references/workflows/patterns.md +++ b/external/develop/devops/cloudflare/references/workflows/patterns.md @@ -2,7 +2,7 @@ ## Design Decisions -Read [Rules of Workflows](https://developers.cloudflare.com/workflows/build/rules-of-workflows/) before choosing step boundaries or concurrency patterns. +Read [Rules of Workflows](https://developers.cloudflare.com/workflows/build/rules-of-workflows/index.md) before choosing step boundaries or concurrency patterns. - Separate work into steps that can be retried independently. Persist results through step returns and keep side effects inside steps. - Make side effects safe to repeat. A retry can happen after an external write succeeds; use the destination's idempotency mechanism or atomic deduplication. A separate check followed by a write does not itself guarantee idempotency. @@ -14,16 +14,16 @@ Read [Rules of Workflows](https://developers.cloudflare.com/workflows/build/rule | Task | Documentation | | --- | --- | -| Process images with human approval; handle approval events and timeouts | [Human-in-the-loop image tagging](https://developers.cloudflare.com/workflows/examples/wait-for-event/) | -| Implement a payment and notification sequence | [Pay cart and send invoice](https://developers.cloudflare.com/workflows/examples/send-invoices/) | -| Export data to object storage | [Export and save D1 database](https://developers.cloudflare.com/workflows/examples/backup-d1/) | -| Delay lifecycle follow-ups or retry transient failures | [Sleeping and retrying](https://developers.cloudflare.com/workflows/build/sleeping-and-retrying/) | -| Schedule jobs or start child Workflows | [Trigger Workflows](https://developers.cloudflare.com/workflows/build/trigger-workflows/) | -| Design parallel work, races, conditional steps, and batch creation | [Rules of Workflows](https://developers.cloudflare.com/workflows/build/rules-of-workflows/) | +| Process images with human approval; handle approval events and timeouts | [Human-in-the-loop image tagging](https://developers.cloudflare.com/workflows/examples/wait-for-event/index.md) | +| Implement a payment and notification sequence | [Pay cart and send invoice](https://developers.cloudflare.com/workflows/examples/send-invoices/index.md) | +| Export data to object storage | [Export and save D1 database](https://developers.cloudflare.com/workflows/examples/backup-d1/index.md) | +| Delay lifecycle follow-ups or retry transient failures | [Sleeping and retrying](https://developers.cloudflare.com/workflows/build/sleeping-and-retrying/index.md) | +| Schedule jobs or start child Workflows | [Trigger Workflows](https://developers.cloudflare.com/workflows/build/trigger-workflows/index.md) | +| Design parallel work, races, conditional steps, and batch creation | [Rules of Workflows](https://developers.cloudflare.com/workflows/build/rules-of-workflows/index.md) | ## Testing Workflows -Fetch [Vitest setup](https://developers.cloudflare.com/workers/testing/vitest-integration/write-your-first-test/) for current dependencies and configuration, then use the [Workflow test APIs](https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/#workflows) for introspection, step/event mocks, sleep controls, and cleanup. +Fetch [Vitest setup](https://developers.cloudflare.com/workers/testing/vitest-integration/write-your-first-test/index.md) for current dependencies and configuration, then use the [Workflow test APIs](https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/index.md#workflows) for introspection, step/event mocks, sleep controls, and cleanup. Test retry behavior, event arrival and timeout paths, and duplicate external effects. Use documented introspection waits to observe completion rather than assuming a newly created instance has finished. diff --git a/external/develop/devops/cloudflare/references/zaraz/IMPLEMENTATION_SUMMARY.md b/external/develop/devops/cloudflare/references/zaraz/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index dd8da19..0000000 --- a/external/develop/devops/cloudflare/references/zaraz/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,121 +0,0 @@ -# Zaraz Reference Implementation Summary - -## Files Created - -| File | Lines | Purpose | -|------|-------|---------| -| README.md | 111 | Navigation, decision tree, quick start | -| api.md | 287 | Web API reference, Zaraz Context | -| configuration.md | 307 | Dashboard setup, triggers, tools, consent | -| patterns.md | 430 | SPA, e-commerce, Worker integration | -| gotchas.md | 317 | Troubleshooting, limits, tool-specific issues | -| **Total** | **1,452** | **vs 366 original** | - -## Key Improvements Applied - -### Structure -- ✅ Created 5-file progressive disclosure system -- ✅ Added navigation table in README -- ✅ Added decision tree for routing -- ✅ Added "Reading Order by Task" guide -- ✅ Cross-referenced files throughout - -### New Content Added -- ✅ Zaraz Context (system/client properties) -- ✅ History Change trigger for SPA tracking -- ✅ Context Enrichers pattern -- ✅ Worker Variables pattern -- ✅ Consent management deep dive -- ✅ Tool-specific quirks (GA4, Facebook, Google Ads) -- ✅ GTM migration guide -- ✅ Comprehensive troubleshooting -- ✅ "When NOT to use Zaraz" section -- ✅ TypeScript type definitions - -### Preserved Content -- ✅ All original API methods -- ✅ E-commerce tracking examples -- ✅ Consent management -- ✅ Workers integration (expanded) -- ✅ Common patterns (expanded) -- ✅ Debugging tools -- ✅ Reference links - -## Progressive Disclosure Impact - -### Before (Monolithic) -All tasks loaded 366 lines regardless of need. - -### After (Progressive) -- **Track event task**: README (111) + api.md (287) = 398 lines -- **Debug issue**: gotchas.md (317) = 317 lines (13% reduction) -- **Configure tool**: configuration.md (307) = 307 lines (16% reduction) -- **SPA tracking**: README + patterns.md (SPA section) ~180 lines (51% reduction) - -**Net effect:** Task-specific loading reduces unnecessary content by 13-51% depending on use case. - -## File Summary - -### README.md (111 lines) -- Overview and core concepts -- Quick start guide -- When to use Zaraz vs Workers -- Navigation table -- Reading order by task -- Decision tree - -### api.md (287 lines) -- zaraz.track() -- zaraz.set() -- zaraz.ecommerce() -- Zaraz Context (system/client properties) -- zaraz.consent API -- zaraz.debug -- Cookie methods -- TypeScript definitions - -### configuration.md (307 lines) -- Dashboard setup flow -- Trigger types (including History Change) -- Tool configuration (GA4, Facebook, Google Ads) -- Actions and action rules -- Selective loading -- Consent management setup -- Privacy features -- Testing workflow - -### patterns.md (430 lines) -- SPA tracking (React, Vue, Next.js) -- User identification flows -- Complete e-commerce funnel -- A/B testing -- Worker integration (Context Enrichers, Worker Variables, HTML injection) -- Multi-tool coordination -- GTM migration -- Best practices - -### gotchas.md (317 lines) -- Events not firing (5-step debug process) -- Consent issues -- SPA tracking pitfalls -- Performance issues -- Tool-specific quirks -- Data layer issues -- Limits table -- When NOT to use Zaraz -- Debug checklist - -## Quality Metrics - -- ✅ All files use consistent markdown formatting -- ✅ Code examples include language tags -- ✅ Tables for structured data (limits, parameters, comparisons) -- ✅ Problem → Cause → Solution format in gotchas -- ✅ Cross-references between files -- ✅ No "see documentation" placeholders -- ✅ Real, actionable examples throughout -- ✅ Verified API syntax for Workers - -## Original Backup - -Original SKILL.md preserved as `_SKILL_old.md` for reference. diff --git a/external/develop/devops/cloudflare/references/zaraz/README.md b/external/develop/devops/cloudflare/references/zaraz/README.md deleted file mode 100644 index 0e28155..0000000 --- a/external/develop/devops/cloudflare/references/zaraz/README.md +++ /dev/null @@ -1,111 +0,0 @@ -# Cloudflare Zaraz - -Expert guidance for Cloudflare Zaraz - server-side tag manager for loading third-party tools at the edge. - -## What is Zaraz? - -Zaraz offloads third-party scripts (analytics, ads, chat, marketing) to Cloudflare's edge, improving site speed, privacy, and security. Zero client-side performance impact. - -**Core Concepts:** -- **Server-side execution** - Scripts run on Cloudflare, not user's browser -- **Single HTTP request** - All tools loaded via one endpoint -- **Privacy-first** - Control data sent to third parties -- **No client-side JS overhead** - Minimal browser impact - -## Quick Start - -1. Navigate to domain > Zaraz in Cloudflare dashboard -2. Click "Start setup" -3. Add tools (Google Analytics, Facebook Pixel, etc.) -4. Configure triggers (when tools fire) -5. Add tracking code to your site: - -```javascript -// Track page view -zaraz.track('page_view'); - -// Track custom event -zaraz.track('button_click', { button_id: 'cta' }); - -// Set user properties -zaraz.set('userId', 'user_123'); -``` - -## When to Use Zaraz - -**Use Zaraz when:** -- Adding multiple third-party tools (analytics, ads, marketing) -- Site performance is critical (no client-side JS overhead) -- Privacy compliance required (GDPR, CCPA) -- Non-technical teams need to manage tools - -**Use Workers directly when:** -- Building custom server-side tracking logic -- Need full control over data processing -- Integrating with complex backend systems -- Zaraz's tool library doesn't meet needs - -## In This Reference - -| File | Purpose | When to Read | -|------|---------|--------------| -| [api.md](./api.md) | Web API, zaraz object, consent methods | Implementing tracking calls | -| [configuration.md](./configuration.md) | Dashboard setup, triggers, tools | Initial setup, adding tools | -| [patterns.md](./patterns.md) | SPA, e-commerce, Worker integration | Best practices, common scenarios | -| [gotchas.md](./gotchas.md) | Troubleshooting, limits, pitfalls | Debugging issues | - -## Reading Order by Task - -| Task | Files to Read | -|------|---------------| -| Add analytics to site | README → configuration.md | -| Track custom events | README → api.md | -| Debug tracking issues | gotchas.md | -| SPA tracking | api.md → patterns.md (SPA section) | -| E-commerce tracking | api.md#ecommerce → patterns.md#ecommerce | -| Worker integration | patterns.md#worker-integration | -| GDPR compliance | api.md#consent → configuration.md#consent | - -## Decision Tree - -``` -What do you need? - -├─ Track events in browser → api.md -│ ├─ Page views, clicks → zaraz.track() -│ ├─ User properties → zaraz.set() -│ └─ E-commerce → zaraz.ecommerce() -│ -├─ Configure Zaraz → configuration.md -│ ├─ Add GA4/Facebook → tools setup -│ ├─ When tools fire → triggers -│ └─ GDPR consent → consent purposes -│ -├─ Integrate with Workers → patterns.md#worker-integration -│ ├─ Enrich context → Context Enrichers -│ └─ Inject tracking → HTML rewriting -│ -└─ Debug issues → gotchas.md - ├─ Events not firing → troubleshooting - ├─ Consent issues → consent debugging - └─ Performance → debugging tools -``` - -## Key Features - -- **100+ Pre-built Tools** - GA4, Facebook, Google Ads, TikTok, etc. -- **Zero Client Impact** - Runs at Cloudflare's edge, not browser -- **Privacy Controls** - Consent management, data filtering -- **Custom Tools** - Build Managed Components for proprietary systems -- **Worker Integration** - Enrich context, compute dynamic values -- **Debug Mode** - Real-time event inspection - -## Reference - -- [Zaraz Docs](https://developers.cloudflare.com/zaraz/) -- [Web API](https://developers.cloudflare.com/zaraz/web-api/) -- [Managed Components](https://developers.cloudflare.com/zaraz/advanced/load-custom-managed-component/) - ---- - -This skill focuses exclusively on Zaraz. For Workers development, see `cloudflare-workers` skill. diff --git a/external/develop/devops/cloudflare/references/zaraz/api.md b/external/develop/devops/cloudflare/references/zaraz/api.md deleted file mode 100644 index 5d8e1cc..0000000 --- a/external/develop/devops/cloudflare/references/zaraz/api.md +++ /dev/null @@ -1,112 +0,0 @@ -# Zaraz Web API - -Client-side JavaScript API for tracking events, setting properties, and managing consent. - -## zaraz.track() - -```javascript -zaraz.track('button_click'); -zaraz.track('purchase', { value: 99.99, currency: 'USD', item_id: '12345' }); -zaraz.track('pageview', { page_path: '/products', page_title: 'Products' }); // SPA -``` - -**Params:** `eventName` (string), `properties` (object, optional). Fire-and-forget. - -## zaraz.set() - -```javascript -zaraz.set('userId', 'user_12345'); -zaraz.set({ email: '[email protected]', plan: 'premium', country: 'US' }); -``` - -Properties persist for page session. Use for user identification and segmentation. - -## zaraz.ecommerce() - -```javascript -zaraz.ecommerce('Product Viewed', { product_id: 'SKU123', name: 'Widget', price: 49.99 }); -zaraz.ecommerce('Product Added', { product_id: 'SKU123', quantity: 2, price: 49.99 }); -zaraz.ecommerce('Order Completed', { - order_id: 'ORD-789', total: 149.98, currency: 'USD', - products: [{ product_id: 'SKU123', quantity: 2, price: 49.99 }] -}); -``` - -**Events:** `Product Viewed`, `Product Added`, `Product Removed`, `Cart Viewed`, `Checkout Started`, `Order Completed` - -Tools auto-map to GA4, Facebook CAPI, etc. - -## System Properties (Triggers) - -``` -{{system.page.url}} {{system.page.title}} {{system.page.referrer}} -{{system.device.ip}} {{system.device.userAgent}} {{system.device.language}} -{{system.cookies.name}} {{client.__zarazTrack.userId}} -``` - -## zaraz.consent - -```javascript -// Check -const purposes = zaraz.consent.getAll(); // { analytics: true, marketing: false } - -// Set -zaraz.consent.modal = true; // Show modal -zaraz.consent.setAll({ analytics: true, marketing: false }); -zaraz.consent.set('marketing', true); - -// Listen -zaraz.consent.addEventListener('consentChanged', () => { - if (zaraz.consent.getAll().marketing) zaraz.track('marketing_consent_granted'); -}); -``` - -**Flow:** Configure purposes in dashboard → Map tools to purposes → Show modal/set programmatically → Tools fire when allowed - -## zaraz.debug - -```javascript -zaraz.debug = true; -zaraz.track('test_event'); -console.log(zaraz.tools); // View loaded tools -``` - -## Cookie Methods - -```javascript -zaraz.getCookie('session_id'); // Zaraz namespace -zaraz.readCookie('_ga'); // Any cookie -``` - -## Async Behavior - -All methods fire-and-forget. Events batched and sent asynchronously: - -```javascript -zaraz.track('event1'); -zaraz.set('prop', 'value'); -zaraz.track('event2'); // All batched -``` - -## TypeScript Types - -```typescript -interface Zaraz { - track(event: string, properties?: Record): void; - set(key: string, value: unknown): void; - set(properties: Record): void; - ecommerce(event: string, properties: Record): void; - consent: { - getAll(): Record; - setAll(purposes: Record): void; - set(purpose: string, value: boolean): void; - addEventListener(event: 'consentChanged', callback: () => void): void; - modal: boolean; - }; - debug: boolean; - tools?: string[]; - getCookie(name: string): string | undefined; - readCookie(name: string): string | undefined; -} -declare global { interface Window { zaraz: Zaraz; } } -``` diff --git a/external/develop/devops/cloudflare/references/zaraz/configuration.md b/external/develop/devops/cloudflare/references/zaraz/configuration.md deleted file mode 100644 index e2e534c..0000000 --- a/external/develop/devops/cloudflare/references/zaraz/configuration.md +++ /dev/null @@ -1,90 +0,0 @@ -# Zaraz Configuration - -## Dashboard Setup - -1. Domain → Zaraz → Start setup -2. Add tool (e.g., Google Analytics 4) -3. Enter credentials (GA4: `G-XXXXXXXXXX`) -4. Configure triggers -5. Save and Publish - -## Triggers - -| Type | When | Use Case | -|------|------|----------| -| Pageview | Page load | Track page views | -| Click | Element clicked | Button tracking | -| Form Submission | Form submitted | Lead capture | -| History Change | URL changes (SPA) | React/Vue routing | -| Variable Match | Custom condition | Conditional firing | - -### History Change (SPA) - -``` -Type: History Change -Event: pageview -``` - -Fires on `pushState`, `replaceState`, hash changes. **No manual tracking needed.** - -### Click Trigger - -``` -Type: Click -CSS Selector: .buy-button -Event: purchase_intent -Properties: - button_text: {{system.clickElement.text}} -``` - -## Tool Configuration - -**GA4:** -``` -Measurement ID: G-XXXXXXXXXX -Events: page_view, purchase, user_engagement -``` - -**Facebook Pixel:** -``` -Pixel ID: 1234567890123456 -Events: PageView, Purchase, AddToCart -``` - -**Google Ads:** -``` -Conversion ID: AW-XXXXXXXXX -Conversion Label: YYYYYYYYYY -``` - -## Consent Management - -1. Settings → Consent → Create purposes (analytics, marketing) -2. Map tools to purposes -3. Set behavior: "Do not load until consent granted" - -**Programmatic consent:** -```javascript -zaraz.consent.setAll({ analytics: true, marketing: true }); -``` - -## Privacy Features - -| Feature | Default | -|---------|---------| -| IP Anonymization | Enabled | -| Cookie Control | Via consent purposes | -| GDPR/CCPA | Consent modal | - -## Testing - -1. **Preview Mode** - test without publishing -2. **Debug Mode** - `zaraz.debug = true` -3. **Network tab** - filter "zaraz" - -## Limits - -| Resource | Limit | -|----------|-------| -| Event properties | 100KB | -| Consent purposes | 20 | diff --git a/external/develop/devops/cloudflare/references/zaraz/gotchas.md b/external/develop/devops/cloudflare/references/zaraz/gotchas.md deleted file mode 100644 index eaa6b49..0000000 --- a/external/develop/devops/cloudflare/references/zaraz/gotchas.md +++ /dev/null @@ -1,81 +0,0 @@ -# Zaraz Gotchas - -## Events Not Firing - -**Check:** -1. Tool enabled in dashboard (green dot) -2. Trigger conditions met -3. Consent granted for tool's purpose -4. Tool credentials correct (GA4: `G-XXXXXXXXXX`, FB: numeric only) - -**Debug:** -```javascript -zaraz.debug = true; -console.log('Tools:', zaraz.tools); -console.log('Consent:', zaraz.consent.getAll()); -``` - -## Consent Issues - -**Modal not showing:** -```javascript -// Clear consent cookie -document.cookie = 'zaraz-consent=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; -location.reload(); -``` - -**Tools firing before consent:** Map tool to consent purpose with "Do not load until consent granted". - -## SPA Tracking - -**Route changes not tracked:** -1. Configure History Change trigger in dashboard -2. Hash routing (`#/path`) requires manual tracking: -```javascript -window.addEventListener('hashchange', () => { - zaraz.track('pageview', { page_path: location.pathname + location.hash }); -}); -``` - -**React fix:** -```javascript -const location = useLocation(); -useEffect(() => { - zaraz.track('pageview', { page_path: location.pathname }); -}, [location]); // Include dependency -``` - -## Performance - -**Slow page load:** -- Audit tool count (50+ degrades performance) -- Disable blocking triggers unless required -- Reduce event payload size (<100KB) - -## Tool-Specific Issues - -| Tool | Issue | Fix | -|------|-------|-----| -| GA4 | Events not in real-time | Wait 5-10 min, use DebugView | -| Facebook | Invalid Pixel ID | Use numeric only (no `fbpx_` prefix) | -| Google Ads | Conversions not attributed | Include `send_to: 'AW-XXX/LABEL'` | - -## Data Layer - -- Properties persist per page only - set on each page load -- Nested access: `{{client.__zarazTrack.user.plan}}` - -## Limits - -| Resource | Limit | -|----------|-------| -| Request size | 100KB | -| Consent purposes | 20 | -| API rate | 1000 req/sec | - -## When NOT to Use Zaraz - -- Server-to-server tracking (use Workers) -- Real-time bidirectional communication -- Binary data transmission -- Authentication flows diff --git a/external/develop/devops/cloudflare/references/zaraz/patterns.md b/external/develop/devops/cloudflare/references/zaraz/patterns.md deleted file mode 100644 index c5ef967..0000000 --- a/external/develop/devops/cloudflare/references/zaraz/patterns.md +++ /dev/null @@ -1,74 +0,0 @@ -# Zaraz Patterns - -## SPA Tracking - -**History Change Trigger (Recommended):** Configure in dashboard - no code needed, Zaraz auto-detects route changes. - -**Manual tracking (React/Vue/Next.js):** -```javascript -// On route change -zaraz.track('pageview', { page_path: pathname, page_title: document.title }); -``` - -## User Identification - -```javascript -// Login -zaraz.set({ userId: user.id, email: user.email, plan: user.plan }); -zaraz.track('login', { method: 'oauth' }); - -// Logout - set to null (cannot clear) -zaraz.set('userId', null); -``` - -## E-commerce Funnel - -| Event | Method | -|-------|--------| -| View | `zaraz.ecommerce('Product Viewed', { product_id, name, price })` | -| Add to cart | `zaraz.ecommerce('Product Added', { product_id, quantity })` | -| Checkout | `zaraz.ecommerce('Checkout Started', { cart_id, products: [...] })` | -| Purchase | `zaraz.ecommerce('Order Completed', { order_id, total, products })` | - -## A/B Testing - -```javascript -zaraz.set('experiment_checkout', variant); -zaraz.track('experiment_viewed', { experiment_id: 'checkout', variant }); -// On conversion -zaraz.track('experiment_conversion', { experiment_id, variant, value }); -``` - -## Worker Integration - -**Context Enricher** - Modify context before tools execute: -```typescript -export default { - async fetch(request, env) { - const body = await request.json(); - body.system.userRegion = request.cf?.region; - return Response.json(body); - } -}; -``` -Configure: Zaraz > Settings > Context Enrichers - -**Worker Variables** - Compute dynamic values server-side, use as `{{worker.variable_name}}`. - -## GTM Migration - -| GTM | Zaraz | -|-----|-------| -| `dataLayer.push({event: 'purchase'})` | `zaraz.ecommerce('Order Completed', {...})` | -| `{{Page URL}}` | `{{system.page.url}}` | -| `{{Page Title}}` | `{{system.page.title}}` | -| Page View trigger | Pageview trigger | -| Click trigger | Click (selector: `*`) | - -## Best Practices - -1. Use dashboard triggers over inline code -2. Enable History Change for SPAs (no manual code) -3. Debug with `zaraz.debug = true` -4. Implement consent early (GDPR/CCPA) -5. Use Context Enrichers for sensitive/server data diff --git a/external/develop/devops/devops-engineer/SKILL.md b/external/develop/devops/devops-engineer/SKILL.md index 3629161..c5f36bb 100644 --- a/external/develop/devops/devops-engineer/SKILL.md +++ b/external/develop/devops/devops-engineer/SKILL.md @@ -4,6 +4,7 @@ description: Creates Dockerfiles, configures CI/CD pipelines, writes Kubernetes license: MIT metadata: author: https://github.com/Jeffallan + company: https://synergetic.solutions version: "1.2.0" domain: devops triggers: DevOps, CI/CD, deployment, Docker, Kubernetes, Terraform, GitHub Actions, infrastructure, platform engineering, incident response, on-call, self-service @@ -145,4 +146,6 @@ Always document the rollback command and verification step in the PR or change t GitHub Actions, GitLab CI, Jenkins, CircleCI, Docker, Kubernetes, Helm, ArgoCD, Flux, Terraform, Pulumi, Crossplane, AWS/GCP/Azure, Prometheus, Grafana, PagerDuty, Backstage, LaunchDarkly, Flagger +Maintained by [@jeffallan](https://github.com/jeffallan), Principal Consultant at [Synergetic Solutions](https://synergetic.solutions) + [Documentation](https://jeffallan.github.io/claude-skills/skills/devops/devops-engineer/) diff --git a/external/develop/devops/devops-engineer/references/docker-patterns.md b/external/develop/devops/devops-engineer/references/docker-patterns.md index f5523e7..3c18637 100644 --- a/external/develop/devops/devops-engineer/references/docker-patterns.md +++ b/external/develop/devops/devops-engineer/references/docker-patterns.md @@ -63,7 +63,7 @@ services: ports: - "3000:3000" environment: - - DATABASE_URL=postgres://user:pass@db:5432/app + - DATABASE_URL=postgres://user:@db:5432/app depends_on: db: condition: service_healthy @@ -72,7 +72,7 @@ services: image: postgres:16-alpine environment: POSTGRES_USER: user - POSTGRES_PASSWORD: pass + POSTGRES_PASSWORD: POSTGRES_DB: app volumes: - postgres_data:/var/lib/postgresql/data diff --git a/external/develop/devops/devops-engineer/references/kubernetes.md b/external/develop/devops/devops-engineer/references/kubernetes.md index 1bb44f7..f4b0504 100644 --- a/external/develop/devops/devops-engineer/references/kubernetes.md +++ b/external/develop/devops/devops-engineer/references/kubernetes.md @@ -103,7 +103,7 @@ metadata: name: app-secrets type: Opaque stringData: - database-url: "postgres://user:pass@host:5432/db" + database-url: "postgres://:@host:5432/db" ``` ## Horizontal Pod Autoscaler diff --git a/external/develop/devops/gcloud/SKILL.md b/external/develop/devops/gcloud/SKILL.md index 0d3a184..c9b7cfb 100644 --- a/external/develop/devops/gcloud/SKILL.md +++ b/external/develop/devops/gcloud/SKILL.md @@ -1,6 +1,7 @@ --- name: gcloud metadata: + version: "1.0.3" category: CloudInfrastructureAndServices description: >- Provides safety-critical validation, guardrails, and data reduction for gcloud @@ -72,6 +73,19 @@ interacting with the Google Cloud SDK (`gcloud` CLI). Following these rules is critical to avoid hallucinated commands, flags, flag values, and positional argument syntax, prevent destructive actions, and minimize context window usage. +## Usage Attribution + +**MUST READ**: Before executing any `gcloud`, REST API, or MCP command, you +MUST read and follow the instructions in +[Usage Attribution](references/attribution.md). If a more specific product or +domain skill (e.g., `cloud-run-basics`, `bigquery-basics`, `gke-basics`) is +also loaded or active for the current task, **ALWAYS** use that specific +skill's `skill:` attribution tag instead of `skill:gcloud`. For commands +executed directly in the terminal when no product-specific skill applies (never +in generated scripts, Terraform, or user code), prefix `gcloud` with +`CLOUDSDK_METRICS_REQUEST_ATTRIBUTION="agent-skills/1.0 (skill:gcloud)"` +and append `agent-skills/1.0 (skill:gcloud)` to `User-Agent` headers. + ## Execution Modes AI agents can interact with Google Cloud resources in two primary ways: @@ -195,6 +209,15 @@ human-in-the-loop authorization: * **No Proactive API Enabling**: Assume necessary APIs are enabled. To prevent unexpected resource provisioning or billing charges, do not proactively try to enable APIs. User approval is required to enable any API. +* **No Proactive `gcloud` CLI Installation** (Host Modification): Never + autonomously install the Google Cloud CLI (`gcloud` CLI) or additional + `gcloud` CLI components (`gcloud components install`, `gcloud components + update`, or system package manager installs of the `gcloud` CLI). If the + `gcloud` binary or a required CLI component is missing, you **must** inform + the user, ask for explicit user confirmation (e.g., *"I am ready to install + the Google Cloud CLI (`gcloud`). Do you want me to proceed?"*), and + **strictly stop** to wait for the user's positive affirmation before + executing any installation commands. * **`gcloud * delete`** (Destructive): Irreversible resource destruction (e.g., project deletion) or data wiping. * **`gcloud billing *`** (Financial): Risk of service disruption or unbounded @@ -270,3 +293,7 @@ for guidance on using the gcloud CLI in automation. - [MCP Usage](references/mcp-usage.md): Using the Cloud CLI remote MCP server (`run_gcloud_command`), project parameter scoping, input files, and execution guidelines. + +- [Usage Attribution](references/attribution.md): Standardized usage + attribution tags and multi-skill precedence rules for `gcloud` CLI, REST + API, and MCP requests. diff --git a/external/develop/devops/gcloud/references/attribution.md b/external/develop/devops/gcloud/references/attribution.md new file mode 100644 index 0000000..a4cb1e4 --- /dev/null +++ b/external/develop/devops/gcloud/references/attribution.md @@ -0,0 +1,48 @@ +# Usage Attribution + +**MUST READ**: Before executing any `gcloud`, REST API, or MCP command with +this skill, follow these usage attribution instructions. Tag every command you +execute directly in the terminal so usage can be attributed. The tag identifies +only the skill and its version; it carries no user data. + +- **Skill Precedence (Multi-Skill Sessions)**: `gcloud` is a foundational + CLI skill frequently pre-bundled or co-loaded alongside product-specific + skills. If a more specific product, service, or domain skill (for example, + `cloud-run-basics`, `cloud-build-basics`, `bigquery-basics`, or + `gke-basics`) is also loaded or active for the current task, **ALWAYS** use + that specific skill's `skill:` attribution tag instead of + `skill:gcloud`. Only use `skill:gcloud` when no product-specific skill + applies to the command being executed. + +- **`gcloud` CLI Commands**: Prefix every `gcloud` invocation, whatever the + subcommand, with the metrics environment variable. Set it inline on each + command because shell state may not persist between commands: + + ```bash + CLOUDSDK_METRICS_REQUEST_ATTRIBUTION="agent-skills/1.0 (skill:gcloud)" \ + gcloud [flags] + ``` + + Do not use `gcloud config set` for this: it would persist beyond the current + task and mislabel unrelated usage. + +- **Direct HTTP, REST, and MCP Requests**: On direct HTTP calls to Google + Cloud APIs (for example with `curl`) or HTTP requests to a remote MCP + server, include `agent-skills/1.0 (skill:gcloud)` in the `User-Agent` + header. If a `User-Agent` header is already specified, append this string to + the end of it rather than overwriting existing `User-Agent` data — the + collection pipeline parses the `agent-skills/` and `skill:` + tokens, so any rewording breaks attribution: + + ``` + User-Agent: agent-skills/1.0 (skill:gcloud) + ``` + +- **Constraint for Generated Code**: The tracking prefix is strictly for + commands you execute directly in the terminal. Do NOT include this tracking + prefix in generated scripts, Terraform templates, or code provided to the + user for their own repositories. + +- **User Opt-Out**: If the user asks to disable or remove tracking or + attribution tags, omit the `CLOUDSDK_METRICS_REQUEST_ATTRIBUTION` variable + and custom `User-Agent` suffix from all commands. diff --git a/external/develop/devops/gcloud/references/cli-usage.md b/external/develop/devops/gcloud/references/cli-usage.md index 6b8ab5b..42525d1 100644 --- a/external/develop/devops/gcloud/references/cli-usage.md +++ b/external/develop/devops/gcloud/references/cli-usage.md @@ -12,6 +12,21 @@ the authoritative for platform-specific installation instructions (Linux, macOS, Windows, package managers, and container images). +> [!CAUTION] +> **Explicit User Confirmation Required Before Installing `gcloud` CLI:** +> Never install the Google Cloud CLI (`gcloud` CLI) or additional `gcloud` CLI +> components (`gcloud components install` / `gcloud components update`) +> autonomously. +> +> Before executing any installation command or package manager script: +> 1. Inform the user that the `gcloud` CLI (or required component) is not +> installed and summarize the installation method you plan to use. +> 2. Ask the user for explicit consent: `"I am ready to install the Google Cloud +> CLI (gcloud) on your machine. Do you want me to proceed?"` +> 3. **CRITICAL**: The agent **MUST NOT** execute any `gcloud` installation or +> component installation commands during this turn. You must ask the consent +> query and **strictly stop** to wait for the user's positive affirmation. + ### Component Management The `gcloud components` command group manages optional CLI components (such as diff --git a/external/develop/devops/playwright-best-practices/advanced/authentication.md b/external/develop/devops/playwright-best-practices/advanced/authentication.md index 02c2dd7..f38a3b8 100644 --- a/external/develop/devops/playwright-best-practices/advanced/authentication.md +++ b/external/develop/devops/playwright-best-practices/advanced/authentication.md @@ -58,7 +58,7 @@ async function generateAuthState() { await page.getByLabel("Username").fill("testuser@example.com"); await page.getByLabel("Password").fill("secretPass123"); await page.getByRole("button", { name: "Log in" }).click(); - await page.waitForURL("/home"); + await page.waitForURL("**/home"); await context.storageState({ path: ".auth/session.json" }); await browser.close(); diff --git a/external/develop/devops/playwright-best-practices/advanced/mobile-testing.md b/external/develop/devops/playwright-best-practices/advanced/mobile-testing.md index e928bde..f417f53 100644 --- a/external/develop/devops/playwright-best-practices/advanced/mobile-testing.md +++ b/external/develop/devops/playwright-best-practices/advanced/mobile-testing.md @@ -13,7 +13,7 @@ ### Use Built-in Devices ```typescript -import { test, devices } from "@playwright/test"; +import { test, devices, defineConfig } from "@playwright/test"; // Configure in playwright.config.ts export default defineConfig({ @@ -82,25 +82,44 @@ test("tap to interact", async ({ page }) => { ### Swipe -```typescript -test("swipe carousel", async ({ page }) => { - await page.goto("/carousel"); +`touchscreen.tap()` sends both touch start and touch end; a following mouse move does not continue a touch gesture. For apps that handle legacy touch events, dispatch the touch sequence explicitly. These synthetic events are untrusted and do not test native scrolling or apps that require trusted pointer input. - const carousel = page.getByTestId("carousel"); - const box = await carousel.boundingBox(); - - if (box) { - // Swipe left - await page.touchscreen.tap(box.x + box.width - 50, box.y + box.height / 2); - await page.mouse.move(box.x + 50, box.y + box.height / 2); +```typescript +// fixtures/touch.ts +import type { Locator } from "@playwright/test"; - // Or use drag - await carousel.dragTo(carousel, { - sourcePosition: { x: box.width - 50, y: box.height / 2 }, - targetPosition: { x: 50, y: box.height / 2 }, +export async function pan(element: Locator, deltaX: number, deltaY: number) { + const { x, y } = await element.evaluate((target) => { + const box = target.getBoundingClientRect(); + return { x: box.x + box.width / 2, y: box.y + box.height / 2 }; + }); + const touches = [{ identifier: 0, clientX: x, clientY: y }]; + await element.dispatchEvent("touchstart", { + touches, changedTouches: touches, targetTouches: touches, + }); + for (let i = 1; i <= 10; i++) { + const touches = [{ + identifier: 0, + clientX: x + deltaX * i / 10, + clientY: y + deltaY * i / 10, + }]; + await element.dispatchEvent("touchmove", { + touches, changedTouches: touches, targetTouches: touches, }); } + await element.dispatchEvent("touchend", { + touches: [], targetTouches: [], + changedTouches: [{ identifier: 0, clientX: x + deltaX, clientY: y + deltaY }], + }); +} +``` + +```typescript +import { pan } from "./fixtures/touch"; +test("swipe carousel", async ({ page }) => { + await page.goto("/carousel"); + await pan(page.getByTestId("carousel"), -100, 0); await expect(page.getByText("Slide 2")).toBeVisible(); }); ``` @@ -109,7 +128,8 @@ test("swipe carousel", async ({ page }) => { ```typescript // fixtures/touch.fixture.ts -import { test as base, Page } from "@playwright/test"; +import { test as base, type Locator } from "@playwright/test"; +import { pan } from "./touch"; type TouchFixtures = { swipe: ( @@ -119,44 +139,14 @@ type TouchFixtures = { }; export const test = base.extend({ - swipe: async ({ page }, use) => { + swipe: async ({}, use) => { await use(async (element, direction) => { - const box = await element.boundingBox(); - if (!box) throw new Error("Element not visible"); - - const centerX = box.x + box.width / 2; - const centerY = box.y + box.height / 2; - const distance = 100; - - const moves = { - left: { - startX: centerX + distance, - endX: centerX - distance, - y: centerY, - }, - right: { - startX: centerX - distance, - endX: centerX + distance, - y: centerY, - }, - up: { - startX: centerX, - endX: centerX, - startY: centerY + distance, - endY: centerY - distance, - }, - down: { - startX: centerX, - endX: centerX, - startY: centerY - distance, - endY: centerY + distance, - }, - }; - - const move = moves[direction]; - await page.touchscreen.tap(move.startX, move.startY ?? move.y); - await page.mouse.move(move.endX, move.endY ?? move.y, { steps: 10 }); - await page.mouse.up(); + const deltas = { + left: [-100, 0], right: [100, 0], + up: [0, -100], down: [0, 100], + } as const; + const [deltaX, deltaY] = deltas[direction]; + await pan(element, deltaX, deltaY); }); }, }); @@ -164,10 +154,7 @@ export const test = base.extend({ // Usage test("swipe to delete", async ({ page, swipe }) => { await page.goto("/inbox"); - - const message = page.getByTestId("message-1"); - await swipe(message, "left"); - + await swipe(page.getByTestId("message-1"), "left"); await expect(page.getByRole("button", { name: "Delete" })).toBeVisible(); }); ``` @@ -179,18 +166,20 @@ test("long press for context menu", async ({ page }) => { await page.goto("/files"); const file = page.getByText("document.pdf"); - const box = await file.boundingBox(); - - if (box) { - // Touch down - await page.touchscreen.tap(box.x + box.width / 2, box.y + box.height / 2); - - // Hold for 500ms - await page.waitForTimeout(500); + const { x, y } = await file.evaluate((target) => { + const box = target.getBoundingClientRect(); + return { x: box.x + box.width / 2, y: box.y + box.height / 2 }; + }); + const touches = [{ identifier: 0, clientX: x, clientY: y }]; + await file.dispatchEvent("touchstart", { + touches, changedTouches: touches, targetTouches: touches, + }); - // Context menu should appear - await expect(page.getByRole("menu")).toBeVisible(); - } + // The app opens its menu while the touch remains down. + await expect(page.getByRole("menu")).toBeVisible(); + await file.dispatchEvent("touchend", { + touches: [], targetTouches: [], changedTouches: touches, + }); }); ``` @@ -323,13 +312,9 @@ test("pull to refresh", async ({ page }) => { const feed = page.getByTestId("feed"); const initialFirstItem = await feed.locator("> *").first().textContent(); - // Simulate pull down - const box = await feed.boundingBox(); - if (box) { - await page.touchscreen.tap(box.x + box.width / 2, box.y + 50); - await page.mouse.move(box.x + box.width / 2, box.y + 200, { steps: 20 }); - await page.mouse.up(); - } + // Use the pan helper above for an app-level touch-event refresh handler. + // This does not simulate the browser's native pull-to-refresh behavior. + await pan(feed, 0, 150); // Wait for refresh await expect(page.getByTestId("loading")).toBeVisible(); diff --git a/external/develop/devops/playwright-best-practices/advanced/network-advanced.md b/external/develop/devops/playwright-best-practices/advanced/network-advanced.md index fa017fe..609e140 100644 --- a/external/develop/devops/playwright-best-practices/advanced/network-advanced.md +++ b/external/develop/devops/playwright-best-practices/advanced/network-advanced.md @@ -398,8 +398,8 @@ Use `context.setOffline(true/false)` to simulate network connectivity changes. > **For comprehensive offline testing patterns:** > -> - **Network failure simulation** (error recovery, graceful degradation): See [error-testing.md](error-testing.md#offline-testing) -> - **Offline-first/PWA testing** (service workers, caching, background sync): See [service-workers.md](service-workers.md#offline-testing) +> - **Network failure simulation** (error recovery, graceful degradation): See [error-testing.md](../debugging/error-testing.md#offline-testing) +> - **Offline-first/PWA testing** (service workers, caching, background sync): See [service-workers.md](../browser-apis/service-workers.md#offline-testing) ### Network Throttling Fixture diff --git a/external/develop/devops/playwright-best-practices/browser-apis/browser-apis.md b/external/develop/devops/playwright-best-practices/browser-apis/browser-apis.md index cc4c269..bd717b1 100644 --- a/external/develop/devops/playwright-best-practices/browser-apis/browser-apis.md +++ b/external/develop/devops/playwright-best-practices/browser-apis/browser-apis.md @@ -156,16 +156,16 @@ test("video call with permissions", async ({ context }) => { // All permissions should be granted const permissions = await page.evaluate(async () => ({ - camera: await navigator.permissions.query({ + camera: (await navigator.permissions.query({ name: "camera" as PermissionName, - }), - microphone: await navigator.permissions.query({ + })).state, + microphone: (await navigator.permissions.query({ name: "microphone" as PermissionName, - }), + })).state, })); - expect(permissions.camera.state).toBe("granted"); - expect(permissions.microphone.state).toBe("granted"); + expect(permissions.camera).toBe("granted"); + expect(permissions.microphone).toBe("granted"); }); ``` diff --git a/external/develop/devops/playwright-best-practices/browser-apis/service-workers.md b/external/develop/devops/playwright-best-practices/browser-apis/service-workers.md index 7603de3..a83f564 100644 --- a/external/develop/devops/playwright-best-practices/browser-apis/service-workers.md +++ b/external/develop/devops/playwright-best-practices/browser-apis/service-workers.md @@ -225,19 +225,9 @@ test("cache updates on new version", async ({ page }) => { return keys.map((r) => r.url); }); - // Simulate app update by mocking SW response - await page.route("**/sw.js", (route) => { - route.fulfill({ - contentType: "application/javascript", - body: ` - const VERSION = 'v2'; - self.addEventListener('install', (e) => { - e.waitUntil(caches.open('app-cache-v2')); - self.skipWaiting(); - }); - `, - }); - }); + // Before this step, deploy the next build so /sw.js returns the v2 worker. + // page.route() and context.route() cannot intercept the browser's update + // request for the service worker script. // Trigger update await page.evaluate(async () => { @@ -254,7 +244,7 @@ test("cache updates on new version", async ({ page }) => { ## Offline Testing -This section covers **offline-first apps (PWAs)** that are designed to work offline using service workers, caching, and background sync. For testing **unexpected network failures** (error recovery, graceful degradation), see [error-testing.md](error-testing.md#offline-testing). +This section covers **offline-first apps (PWAs)** that are designed to work offline using service workers, caching, and background sync. For testing **unexpected network failures** (error recovery, graceful degradation), see [error-testing.md](../debugging/error-testing.md#offline-testing). ### Simulating Offline Mode @@ -345,14 +335,20 @@ test("handles push subscription", async ({ page, context }) => { // Grant notification permission await context.grantPermissions(["notifications"]); + // Mock the subscription boundary; do not contact a real push service. + await page.addInitScript(() => { + PushManager.prototype.subscribe = async () => ({ + toJSON: () => ({ endpoint: "https://push.example.test/subscription" }), + }) as PushSubscription; + }); await page.goto("/pwa-app"); - // Subscribe to push + // Exercise the mock (not real VAPID key validation or push delivery) const subscription = await page.evaluate(async () => { const reg = await navigator.serviceWorker.ready; const sub = await reg.pushManager.subscribe({ userVisibleOnly: true, - applicationServerKey: "test-key", + // The mocked subscribe method does not validate a VAPID public key. }); return sub.toJSON(); }); @@ -368,17 +364,15 @@ test("handles push notification", async ({ context, page }) => { await context.grantPermissions(["notifications"]); await page.goto("/pwa-app"); - // Wait for SW - const swPromise = context.waitForEvent("serviceworker"); - const sw = await swPromise; + // Registration may have happened during navigation. + const sw = context.serviceWorkers()[0] || + (await context.waitForEvent("serviceworker")); // Simulate push message to service worker await sw.evaluate(async () => { // Dispatch push event const pushEvent = new PushEvent("push", { - data: new PushMessageData( - JSON.stringify({ title: "Test", body: "Push message" }), - ), + data: JSON.stringify({ title: "Test", body: "Push message" }), }); self.dispatchEvent(pushEvent); }); @@ -407,6 +401,7 @@ test("notification click opens page", async ({ context, page }) => { await page.evaluate(async () => { const reg = await navigator.serviceWorker.ready; await reg.showNotification("Test", { + tag: "test-notification", body: "Click me", data: { url: "/notification-target" }, }); @@ -414,11 +409,10 @@ test("notification click opens page", async ({ context, page }) => { // Simulate clicking notification (via SW) const sw = context.serviceWorkers()[0]; - await sw.evaluate(() => { + await sw.evaluate(async () => { + const [notification] = await self.registration.getNotifications({ tag: "test-notification" }); self.dispatchEvent( - new NotificationEvent("notificationclick", { - notification: { data: { url: "/notification-target" } } as any, - }), + new NotificationEvent("notificationclick", { notification }), ); }); @@ -482,7 +476,7 @@ test("sync event fires when online", async ({ context, page }) => { await context.setOffline(false); // Wait for sync to complete - await page.waitForFunction(() => window.syncCompleted, { timeout: 10000 }); + await page.waitForFunction(() => window.syncCompleted, undefined, { timeout: 10000 }); }); ``` @@ -498,7 +492,7 @@ test("sync event fires when online", async ({ context, page }) => { ## Related References -- **Network Failures**: See [error-testing.md](error-testing.md#offline-testing) for unexpected network failure patterns +- **Network Failures**: See [error-testing.md](../debugging/error-testing.md#offline-testing) for unexpected network failure patterns - **Browser APIs**: See [browser-apis.md](browser-apis.md) for permissions - **Network Mocking**: See [network-advanced.md](../advanced/network-advanced.md) for network interception - **Browser Extensions**: See [browser-extensions.md](../testing-patterns/browser-extensions.md) for extension service worker patterns diff --git a/external/develop/devops/playwright-best-practices/browser-apis/websockets.md b/external/develop/devops/playwright-best-practices/browser-apis/websockets.md index 075a997..0e7e747 100644 --- a/external/develop/devops/playwright-best-practices/browser-apis/websockets.md +++ b/external/develop/devops/playwright-best-practices/browser-apis/websockets.md @@ -110,29 +110,13 @@ test("displays incoming chat message", async ({ page }) => { ### Mock WebSocket with Route Handler ```typescript -test("mock websocket entirely", async ({ page, context }) => { - // Intercept the WebSocket upgrade - await context.route("**/ws/**", async (route) => { - // For WebSocket routes, we can't fulfill directly - // Instead, use page.evaluate to mock the client-side - }); - - // Alternative: Mock at application level - await page.addInitScript(() => { - const OriginalWebSocket = window.WebSocket; - (window as any).WebSocket = function (url: string) { - const ws = { - readyState: 1, - send: (data: string) => { - console.log("WS Send:", data); - }, - close: () => {}, - addEventListener: () => {}, - removeEventListener: () => {}, - }; - setTimeout(() => ws.onopen?.(), 100); - return ws; - }; +test("mock websocket entirely", async ({ page }) => { + // Register before the app creates the socket. HTTP route() does not route + // WebSocket frames; routeWebSocket() is available since Playwright 1.48. + await page.routeWebSocket("**/ws/**", (ws) => { + ws.onMessage((message) => { + if (message === "ping") ws.send("pong"); + }); }); await page.goto("/chat"); @@ -143,7 +127,7 @@ test("mock websocket entirely", async ({ page, context }) => { ```typescript // fixtures/websocket.fixture.ts -import { test as base, Page } from "@playwright/test"; +import { test as base, type WebSocketRoute } from "@playwright/test"; type WsMessage = { type: string; [key: string]: any }; @@ -158,34 +142,22 @@ export const test = base.extend({ mockWebSocket: async ({ page }, use) => { const sentMessages: WsMessage[] = []; - // Capture sent messages - await page.addInitScript(() => { - (window as any).__wsSent = []; - const OriginalWebSocket = window.WebSocket; - window.WebSocket = function (url: string) { - const ws = new OriginalWebSocket(url); - const originalSend = ws.send.bind(ws); - ws.send = (data: string) => { - (window as any).__wsSent.push(JSON.parse(data)); - originalSend(data); - }; - (window as any).__ws = ws; - return ws; - } as any; + // This example app opens one JSON chat socket per page. + let resolveSocket!: (socket: WebSocketRoute) => void; + const connected = new Promise((resolve) => { + resolveSocket = resolve; + }); + await page.routeWebSocket("**/ws/**", (socket) => { + socket.onMessage((data) => sentMessages.push(JSON.parse(data.toString()))); + resolveSocket(socket); }); await use({ injectMessage: async (message) => { - await page.evaluate((msg) => { - const event = new MessageEvent("message", { - data: JSON.stringify(msg), - }); - (window as any).__ws?.dispatchEvent(event); - }, message); - }, - getSentMessages: async () => { - return page.evaluate(() => (window as any).__wsSent || []); + const socket = await connected; + socket.send(JSON.stringify(message)); }, + getSentMessages: async () => sentMessages, }); }, }); diff --git a/external/develop/devops/playwright-best-practices/core/assertions-waiting.md b/external/develop/devops/playwright-best-practices/core/assertions-waiting.md index bd03dd8..bac88cb 100644 --- a/external/develop/devops/playwright-best-practices/core/assertions-waiting.md +++ b/external/develop/devops/playwright-best-practices/core/assertions-waiting.md @@ -138,7 +138,7 @@ test("check form", async ({ page }) => { await expect.soft(page.getByRole("form")).toBeVisible(); // Exit early if form not visible (pointless to check fields) - if (expect.soft.hasFailures()) { + if (test.info().errors.length > 0) { return; } diff --git a/external/develop/devops/playwright-best-practices/core/configuration.md b/external/develop/devops/playwright-best-practices/core/configuration.md index 66b9d33..23899c2 100644 --- a/external/develop/devops/playwright-best-practices/core/configuration.md +++ b/external/develop/devops/playwright-best-practices/core/configuration.md @@ -15,7 +15,7 @@ ## CLI Quick Reference ```bash -npx playwright init # scaffold config + first test +npm init playwright@latest # scaffold config + first test npx playwright test --config=custom.config.ts # use alternate config npx playwright test --project=chromium # run single project npx playwright test --reporter=html # override reporter @@ -33,7 +33,7 @@ DEBUG=pw:api npx playwright test # verbose logging |---------|---------|---------|-------------| | Test takes too long overall | `timeout` | 30s | 30-60s (max 120s) | | Assertion retries too long/short | `expect.timeout` | 5s | 5-10s | -| `page.goto()` or `waitForURL()` times out | `navigationTimeout` | 30s | 10-30s | +| `page.goto()` or `waitForURL()` times out | `navigationTimeout` | 0 (unlimited, bounded by test timeout) | 10-30s | | `click()`, `fill()` time out | `actionTimeout` | 0 (unlimited) | 10-15s | | Dev server slow to start | `webServer.timeout` | 60s | 60-180s | diff --git a/external/develop/devops/playwright-best-practices/core/fixtures-hooks.md b/external/develop/devops/playwright-best-practices/core/fixtures-hooks.md index ff9dc93..7e6061d 100644 --- a/external/develop/devops/playwright-best-practices/core/fixtures-hooks.md +++ b/external/develop/devops/playwright-best-practices/core/fixtures-hooks.md @@ -111,7 +111,7 @@ export default defineConfig({ ### Automatic Fixtures ```typescript -export const test = base.extend<{}, { setupDb: void }>({ +export const test = base.extend<{ setupDb: void }>({ // Auto-fixture runs for every test without explicit usage setupDb: [ async ({}, use) => { diff --git a/external/develop/devops/playwright-best-practices/core/global-setup.md b/external/develop/devops/playwright-best-practices/core/global-setup.md index a033522..d1e60e8 100644 --- a/external/develop/devops/playwright-best-practices/core/global-setup.md +++ b/external/develop/devops/playwright-best-practices/core/global-setup.md @@ -398,7 +398,7 @@ Use **worker-scoped fixtures** instead of globalSetup when: | Scenario | Why Fixtures Are Better | | ------------------------------------ | ---------------------------------------------------- | | Each worker needs isolated resources | Fixtures can create per-worker databases, servers | -| Setup needs Playwright APIs | Fixtures have access to `page`, `request`, `browser` | +| Setup needs Playwright APIs | Worker fixtures can use `browser` and `playwright`; `page`, `context`, and `request` are test-scoped | | Setup depends on test configuration | Fixtures receive test context and options | | Resources need cleanup per worker | Worker fixtures auto-cleanup when worker exits | diff --git a/external/develop/devops/playwright-best-practices/core/locators.md b/external/develop/devops/playwright-best-practices/core/locators.md index f806635..ce70e5e 100644 --- a/external/develop/devops/playwright-best-practices/core/locators.md +++ b/external/develop/devops/playwright-best-practices/core/locators.md @@ -183,7 +183,7 @@ Playwright pierces shadow DOM by default: page.getByRole("button", { name: "Shadow Button" }); // Explicit shadow DOM traversal (if needed) -page.locator("my-component").locator("internal:shadow=button"); +page.locator("my-component").locator("button"); ``` ## Iframes diff --git a/external/develop/devops/playwright-best-practices/core/test-data.md b/external/develop/devops/playwright-best-practices/core/test-data.md index 1c07af8..4e5bb13 100644 --- a/external/develop/devops/playwright-best-practices/core/test-data.md +++ b/external/develop/devops/playwright-best-practices/core/test-data.md @@ -407,8 +407,6 @@ type SeedFixtures = { }; export const test = base.extend({ - cleanupUsers: [], - seedUser: async ({ request, cleanupUsers }, use) => { await use(async (overrides = {}) => { const userData = createUser(overrides); @@ -448,7 +446,7 @@ test("user profile page", async ({ page, seedUser }) => { ```typescript // fixtures/db.fixture.ts -export const test = base.extend<{}, { db: DbTransaction }>({ +export const test = base.extend<{ db: DbTransaction }>({ db: [ async ({}, use) => { const client = await pool.connect(); diff --git a/external/develop/devops/playwright-best-practices/debugging/debugging.md b/external/develop/devops/playwright-best-practices/debugging/debugging.md index 187e487..37c002d 100644 --- a/external/develop/devops/playwright-best-practices/debugging/debugging.md +++ b/external/develop/devops/playwright-best-practices/debugging/debugging.md @@ -395,6 +395,20 @@ const frame = page.frameLocator("iframe").first(); console.log(await frame.getByRole("button").count()); ``` +### Browser Executable Missing + +`browserType.launch: Executable doesn't exist` means the browser build for this Playwright version is not installed. Each Playwright version needs one specific build. Builds that other versions installed in `~/.cache/ms-playwright` (Linux) or `~/Library/Caches/ms-playwright` (macOS) do not count. + +```bash +# Show the builds this version needs and where they go +npx playwright install --dry-run + +# Install only the browsers the tests use +npx playwright install chromium +``` + +Do not change the `@playwright/test` version to match a build that is already in the cache. Choose the version for the project, then install its browsers. In Docker, use the image tag that matches the version (see [docker.md](../infrastructure-ci-cd/docker.md)). + ## Logging ### Capture Browser Console diff --git a/external/develop/devops/playwright-best-practices/debugging/error-testing.md b/external/develop/devops/playwright-best-practices/debugging/error-testing.md index 43b17b6..fb48ae0 100644 --- a/external/develop/devops/playwright-best-practices/debugging/error-testing.md +++ b/external/develop/devops/playwright-best-practices/debugging/error-testing.md @@ -162,7 +162,7 @@ test("handles failure during request", async ({ page }) => { ## Offline Testing -This section covers **unexpected network failures** and error recovery. For **offline-first apps (PWAs)** with service workers, caching, and background sync, see [service-workers.md](service-workers.md#offline-testing). +This section covers **unexpected network failures** and error recovery. For **offline-first apps (PWAs)** with service workers, caching, and background sync, see [service-workers.md](../browser-apis/service-workers.md#offline-testing). ### Go Offline During Session diff --git a/external/develop/devops/playwright-best-practices/debugging/flaky-tests.md b/external/develop/devops/playwright-best-practices/debugging/flaky-tests.md index d081f21..0d640ec 100644 --- a/external/develop/devops/playwright-best-practices/debugging/flaky-tests.md +++ b/external/develop/devops/playwright-best-practices/debugging/flaky-tests.md @@ -199,7 +199,7 @@ await responsePromise; await expect(page.locator(".data-row")).toHaveCount(10); ``` -> **For comprehensive waiting strategies** (navigation, element state, network, polling with `toPass()`), see [assertions-waiting.md](assertions-waiting.md#waiting-strategies). +> **For comprehensive waiting strategies** (navigation, element state, network, polling with `toPass()`), see [assertions-waiting.md](../core/assertions-waiting.md#waiting-strategies). **Problem: Complex async state** diff --git a/external/develop/devops/playwright-best-practices/frameworks/angular.md b/external/develop/devops/playwright-best-practices/frameworks/angular.md index ec09ecd..196efdf 100644 --- a/external/develop/devops/playwright-best-practices/frameworks/angular.md +++ b/external/develop/devops/playwright-best-practices/frameworks/angular.md @@ -49,7 +49,7 @@ export default defineConfig({ webServer: { command: process.env.CI - ? 'npx ng build && npx http-server dist/my-app/browser -p 4200 -s' + ? 'npx ng build && npx serve -s dist/my-app/browser -l 4200' : 'npx ng serve', url: 'http://localhost:4200', reuseExistingServer: !process.env.CI, @@ -493,11 +493,11 @@ test('no hydration errors', async ({ page }) => { | Scenario | Command | Notes | |---|---|---| | Local dev | `npx ng serve` | Fast rebuild, source maps | -| CI production | `npx ng build && npx http-server dist/app/browser -p 4200 -s` | Tests production bundle | +| CI production | `npx ng build && npx serve -s dist/app/browser -l 4200` | Tests production bundle with SPA fallback | | CI SSR | `npx ng build --ssr && node dist/app/server/server.mjs` | Tests server-side rendering | | Staging | No `webServer` | Point `baseURL` to staging URL | -The `-s` flag on `http-server` enables SPA fallback for Angular Router. +The `-s` flag on `serve` enables SPA fallback for Angular Router. In `http-server`, `-s` means silent logging, not SPA fallback. ## CDK Overlay Container @@ -520,7 +520,7 @@ const listbox = page.getByRole('listbox'); | `page.evaluate(() => window.ng)` | Not available in production builds | Test through the DOM | | `await page.waitForTimeout(500)` | Zone.js timing varies | Use auto-retrying assertions | | `browser.waitForAngular()` | Does not exist in Playwright | Remove entirely | -| `ng serve` in CI | Slower, includes debug code | Use `ng build && http-server` | +| `ng serve` in CI | Slower, includes debug code | Use `ng build` and serve the production bundle with SPA fallback | ## Related diff --git a/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/other-providers.md b/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/other-providers.md index ca80859..38e015e 100644 --- a/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/other-providers.md +++ b/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/other-providers.md @@ -413,11 +413,20 @@ stages: - task: DownloadPipelineArtifact@2 inputs: patterns: "blob-report-*/**" - path: all-blob-reports + path: downloaded-blob-reports displayName: "Download blob reports" + # Downloading multiple artifacts creates a subdirectory per artifact; + # merge-reports expects the ZIP files directly in its input directory. + - script: | + mkdir -p all-blob-reports + find downloaded-blob-reports -name '*.zip' -exec cp {} all-blob-reports/ \; + displayName: "Flatten blob reports" + - script: npx playwright merge-reports --reporter=html ./all-blob-reports displayName: "Merge reports" + env: + PLAYWRIGHT_HTML_OUTPUT_DIR: pw-report - task: PublishPipelineArtifact@1 inputs: @@ -428,7 +437,7 @@ stages: ## JUnit Reporter Config -All platforms benefit from JUnit output for native test result display: +All platforms benefit from JUnit output for native test result display. The examples above use `pw-report/` for HTML artifacts and `results/junit.xml` for test results, so configure those paths explicitly. In sharded runs, also add `["blob"]` so `merge-reports` has input. ```typescript // playwright.config.ts @@ -438,7 +447,7 @@ export default defineConfig({ reporter: process.env.CI ? [ ["dot"], - ["html", { open: "never" }], + ["html", { outputFolder: "pw-report", open: "never" }], ["junit", { outputFile: "results/junit.xml" }], ] : [["html", { open: "on-failure" }]], diff --git a/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md b/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md index 898a425..95248d3 100644 --- a/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +++ b/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md @@ -286,11 +286,11 @@ jobs: | Tests modify shared database | 1 or isolate | Useful | Sharding splits files; workers run them | | CI has limited resources | 1 or `'25%'` | More | Compensate with more machines | -| Aspect | Workers (in-process) | Shards (across machines) | +| Aspect | Workers (separate processes) | Shards (across machines) | | -------------- | ------------------------- | -------------------------- | | What it splits | Tests across CPU cores | Test files across CI jobs | | Controlled by | Config or `--workers` CLI | `--shard=X/Y` CLI flag | -| Shares memory | Yes | No | +| Shares memory | No | No | | Report merging | Not needed | Required (`merge-reports`) | | Cost | Free (same machine) | More CI minutes | diff --git a/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/performance.md b/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/performance.md index bc03da8..e3a6293 100644 --- a/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/performance.md +++ b/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/performance.md @@ -128,7 +128,7 @@ jobs: Avoid logging in for every test. Use setup projects with storage state to authenticate once and reuse the session. -> **For authentication patterns** (storage state, multiple auth states, setup projects), see [fixtures-hooks.md](fixtures-hooks.md#authentication-patterns). +> **For authentication patterns** (storage state, multiple auth states, setup projects), see [fixtures-hooks.md](../core/fixtures-hooks.md#authentication-patterns). ### Reuse Page State (serial only — trade-off with isolation) diff --git a/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/reporting.md b/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/reporting.md index 55e1a93..ac90ad8 100644 --- a/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/reporting.md +++ b/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/reporting.md @@ -421,4 +421,4 @@ use: { }, ``` -The HTML report embeds screenshots from `test-results/`. Deleting that directory removes screenshots from the report. +The HTML reporter copies screenshot attachments into `playwright-report/data/`. Preserve the entire `playwright-report/` directory when uploading or moving the report; `test-results/` is not required to view those copied attachments. diff --git a/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/test-coverage.md b/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/test-coverage.md index 9bc73ae..7a496ae 100644 --- a/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +++ b/external/develop/devops/playwright-best-practices/infrastructure-ci-cd/test-coverage.md @@ -13,155 +13,94 @@ ### Install Dependencies -```bash -# For V8 coverage (built into Playwright) -# No additional dependencies needed +Playwright's coverage API is Chromium-only. JavaScript coverage entries contain `functions` with V8 execution counts and `source`; CSS coverage entries contain `ranges` and `text`. They are different formats: do not combine them and read `.ranges` from JavaScript entries. -# For Istanbul-based coverage (more features) -npm install -D nyc @istanbuljs/nyc-config-typescript +```bash +npm install -D v8-to-istanbul istanbul-lib-coverage nyc tsx ``` ### Basic Configuration ```typescript // playwright.config.ts -import { defineConfig } from "@playwright/test"; +import { defineConfig, devices } from "@playwright/test"; export default defineConfig({ - use: { - // Enable coverage collection - contextOptions: { - // V8 coverage is automatic with the API below - }, - }, + projects: [ + { name: "coverage", use: { ...devices["Desktop Chrome"] } }, + ], + use: { baseURL: "http://localhost:3000" }, }); ``` ### V8 Coverage Fixture +Collect from the same `page` that the test uses, not an unused page in a separate worker-scoped context. Each test writes a unique output file. Import this fixture instead of `@playwright/test` in tests that need automatic coverage. + ```typescript // fixtures/coverage.ts -import { test as base, expect } from "@playwright/test"; -import fs from "fs"; -import path from "path"; -import { randomUUID } from "crypto"; +import { test as base } from "@playwright/test"; +import fs from "node:fs/promises"; -export const test = base.extend<{}, { collectCoverage: void }>({ +export const test = base.extend<{ collectCoverage: void }>({ collectCoverage: [ - async ({ browser }, use) => { - // Start coverage for all pages - const context = await browser.newContext(); - const page = await context.newPage(); - - await page.coverage.startJSCoverage(); - await page.coverage.startCSSCoverage(); - + async ({ page }, use, testInfo) => { + await page.coverage.startJSCoverage({ resetOnNavigation: false }); + await page.coverage.startCSSCoverage({ resetOnNavigation: false }); await use(); - - // Collect coverage - const [jsCoverage, cssCoverage] = await Promise.all([ + const [js, css] = await Promise.all([ page.coverage.stopJSCoverage(), page.coverage.stopCSSCoverage(), ]); - - // Save coverage data - const coverageDir = "./coverage"; - if (!fs.existsSync(coverageDir)) { - fs.mkdirSync(coverageDir, { recursive: true }); - } - - fs.writeFileSync( - path.join(coverageDir, `coverage-${randomUUID()}.json`), - JSON.stringify([...jsCoverage, ...cssCoverage]) - ); - - await context.close(); + // outputPath creates the test output directory. + await fs.writeFile(testInfo.outputPath("js-coverage.json"), JSON.stringify(js)); + await fs.writeFile(testInfo.outputPath("css-coverage.json"), JSON.stringify(css)); }, - { scope: "worker", auto: true }, + { auto: true }, ], }); + +export { expect } from "@playwright/test"; ``` ## Collecting Coverage ### Per-Test Coverage +For manual collection, do not also enable the automatic fixture on the same page: + ```typescript -test("collect coverage for single test", async ({ page }) => { - // Start coverage collection - await page.coverage.startJSCoverage({ - resetOnNavigation: false, - }); +import { test, expect } from "@playwright/test"; - // Run test +test("collect JavaScript coverage", async ({ page }) => { + await page.coverage.startJSCoverage({ resetOnNavigation: false }); await page.goto("/app"); await page.getByRole("button", { name: "Submit" }).click(); await expect(page.getByText("Success")).toBeVisible(); - - // Stop and get coverage const coverage = await page.coverage.stopJSCoverage(); - - // Filter to only your source files const appCoverage = coverage.filter((entry) => entry.url.includes("/src/")); - console.log(`Covered ${appCoverage.length} source files`); }); ``` ### Coverage for Specific Files -```typescript -test("track specific module coverage", async ({ page }) => { - await page.coverage.startJSCoverage(); - - await page.goto("/checkout"); - await page.getByRole("button", { name: "Pay" }).click(); - - const coverage = await page.coverage.stopJSCoverage(); - - // Find coverage for checkout module - const checkoutCoverage = coverage.find((c) => c.url.includes("checkout.js")); - - if (checkoutCoverage) { - const totalBytes = checkoutCoverage.text?.length || 0; - const coveredBytes = checkoutCoverage.ranges.reduce( - (sum, range) => sum + (range.end - range.start), - 0 - ); - const percentage = (coveredBytes / totalBytes) * 100; - - console.log(`Checkout module: ${percentage.toFixed(1)}% covered`); - expect(percentage).toBeGreaterThan(80); - } -}); -``` +Select the intended module by its served URL. For JavaScript byte/line percentages, convert V8 function ranges to Istanbul rather than adding overlapping V8 ranges or treating them as CSS ranges. ### CSS Coverage +CSS ranges can be used directly for a stylesheet's used-byte estimate: + ```typescript test("collect CSS coverage", async ({ page }) => { await page.coverage.startCSSCoverage(); - await page.goto("/app"); - - // Interact to trigger different CSS states await page.getByRole("button").hover(); - await page.getByRole("dialog").waitFor(); - const cssCoverage = await page.coverage.stopCSSCoverage(); - - // Find unused CSS for (const entry of cssCoverage) { - const totalBytes = entry.text?.length || 0; - const usedBytes = entry.ranges.reduce( - (sum, range) => sum + (range.end - range.start), - 0 - ); - const unusedPercentage = ((totalBytes - usedBytes) / totalBytes) * 100; - - if (unusedPercentage > 50) { - console.warn(`${entry.url}: ${unusedPercentage.toFixed(1)}% unused CSS`); - } + const usedBytes = entry.ranges.reduce((sum, r) => sum + r.end - r.start, 0); + const percentage = entry.text.length === 0 ? 0 : usedBytes / entry.text.length * 100; + console.log(`${entry.url}: ${percentage.toFixed(1)}% CSS used`); } }); ``` @@ -170,326 +109,120 @@ test("collect CSS coverage", async ({ page }) => { ### Converting to Istanbul Format +This script consumes only JavaScript files from the fixture. Supply the captured source to `v8-to-istanbul`: browser script URLs are usually HTTP URLs, not local `file://` URLs. Select your app's served script paths; the `/src/` filter below must match your build. To map bundled code back to original source files, build with inline source maps (for example, `build.sourcemap: "inline"` in Vite or `devtool: "inline-source-map"` in webpack): `v8-to-istanbul` cannot fetch a separate `.map` file from an HTTP URL. The script uses `fs.readdir` with `recursive: true`, which needs Node.js 20.1 or later. + ```typescript // scripts/convert-coverage.ts -import { execSync } from "child_process"; -import fs from "fs"; -import path from "path"; +import fs from "node:fs/promises"; +import path from "node:path"; import v8ToIstanbul from "v8-to-istanbul"; - -async function convertCoverage() { - const coverageDir = "./coverage"; - const files = fs.readdirSync(coverageDir).filter((f) => f.endsWith(".json")); - - const istanbulCoverage: any = {}; - - for (const file of files) { - const coverageData = JSON.parse( - fs.readFileSync(path.join(coverageDir, file), "utf-8") - ); - - for (const entry of coverageData) { - if (!entry.url.startsWith("file://")) continue; - - const filePath = entry.url.replace("file://", ""); - const converter = v8ToIstanbul(filePath); - +import istanbulCoverage from "istanbul-lib-coverage"; +import type { Page } from "@playwright/test"; + +type JSCoverage = Awaited>; +const map = istanbulCoverage.createCoverageMap({}); + +// Optional arguments allow processing downloaded shard output directories. +const roots = process.argv.slice(2); +if (roots.length === 0) roots.push("test-results"); +for (const root of roots) { + const files = await fs.readdir(root, { recursive: true }); + for (const file of files.filter((f) => f.endsWith("js-coverage.json"))) { + const entries: JSCoverage = JSON.parse(await fs.readFile(path.join(root, file), "utf8")); + for (const entry of entries) { + if (!entry.url.includes("/src/") || entry.source === undefined) continue; + const converter = v8ToIstanbul(entry.url, 0, { source: entry.source }); await converter.load(); - converter.applyCoverage(entry.functions || []); - - const istanbul = converter.toIstanbul(); - Object.assign(istanbulCoverage, istanbul); + converter.applyCoverage(entry.functions); + map.merge(converter.toIstanbul()); } } - - fs.writeFileSync( - path.join(coverageDir, "coverage-final.json"), - JSON.stringify(istanbulCoverage) - ); } +await fs.mkdir(".nyc_output", { recursive: true }); +await fs.writeFile(".nyc_output/coverage.json", JSON.stringify(map.toJSON())); +``` + +Run the script as an ES module (for example, set `"type": "module"` in the test project's `package.json`): -convertCoverage(); +```bash +npx tsx scripts/convert-coverage.ts ``` ### Generating HTML Report ```bash -# Using nyc to generate report -npx nyc report --reporter=html --reporter=text --temp-dir=./coverage +npx nyc report --temp-dir=.nyc_output --report-dir=coverage --reporter=html --reporter=text --reporter=lcov --reporter=json-summary ``` -```typescript -// package.json scripts -{ - "scripts": { - "test": "playwright test", - "test:coverage": "playwright test && npm run coverage:report", - "coverage:report": "npx nyc report --reporter=html --reporter=lcov --temp-dir=./coverage" - } -} -``` +`coverage/coverage-summary.json` now contains actual Istanbul line/statement/function/branch summaries, not a byte percentage relabeled as line coverage. ### Custom Coverage Reporter -```typescript -// reporters/coverage-reporter.ts -import type { Reporter, FullResult } from "@playwright/test/reporter"; -import fs from "fs"; -import path from "path"; - -class CoverageReporter implements Reporter { - private coverageData: any[] = []; - - onEnd(result: FullResult) { - // Aggregate all coverage files - const coverageDir = "./coverage"; - const files = fs - .readdirSync(coverageDir) - .filter((f) => f.endsWith(".json")); - - for (const file of files) { - const data = JSON.parse( - fs.readFileSync(path.join(coverageDir, file), "utf-8") - ); - this.coverageData.push(...data); - } - - // Generate summary - const summary = this.generateSummary(); - console.log("\n📊 Coverage Summary:"); - console.log(` Files: ${summary.totalFiles}`); - console.log(` Lines: ${summary.lineCoverage.toFixed(1)}%`); - console.log(` Bytes: ${summary.byteCoverage.toFixed(1)}%`); - - if (summary.lineCoverage < 80) { - console.warn("⚠️ Coverage below 80% threshold!"); - } - } - - private generateSummary() { - let totalBytes = 0; - let coveredBytes = 0; - const files = new Set(); - - for (const entry of this.coverageData) { - if (entry.url.includes("/src/")) { - files.add(entry.url); - totalBytes += entry.text?.length || 0; - coveredBytes += entry.ranges.reduce( - (sum: number, r: any) => sum + (r.end - r.start), - 0 - ); - } - } - - return { - totalFiles: files.size, - byteCoverage: (coveredBytes / totalBytes) * 100, - lineCoverage: (coveredBytes / totalBytes) * 100, // Simplified - }; - } -} - -export default CoverageReporter; -``` +Run conversion/report generation after Playwright exits, so all test-scoped fixture teardowns have written their files. A test's `afterAll` hook is not a global aggregation point across workers. Custom reporters can consume the generated summary after conversion. ## Coverage Thresholds ### Enforcing Minimum Coverage -```typescript -// tests/coverage.spec.ts -import { test, expect } from "@playwright/test"; -import fs from "fs"; -import path from "path"; - -test.afterAll(async () => { - const coverageDir = "./coverage"; - const files = fs.readdirSync(coverageDir).filter((f) => f.endsWith(".json")); - - let totalBytes = 0; - let coveredBytes = 0; - - for (const file of files) { - const coverage = JSON.parse( - fs.readFileSync(path.join(coverageDir, file), "utf-8") - ); - - for (const entry of coverage) { - if (!entry.url.includes("/src/")) continue; - totalBytes += entry.text?.length || 0; - coveredBytes += entry.ranges.reduce( - (sum: number, r: any) => sum + (r.end - r.start), - 0 - ); - } - } - - const coveragePercent = (coveredBytes / totalBytes) * 100; - - // Enforce threshold - expect(coveragePercent).toBeGreaterThan(80); -}); +```bash +npx nyc check-coverage --temp-dir=.nyc_output --lines=80 --statements=80 --functions=80 --branches=70 ``` ### Per-Directory Thresholds -```typescript -// coverage-check.ts -interface CoverageThreshold { - pattern: RegExp; - minCoverage: number; -} +Use nyc's `--include` and `--per-file` options for a selected source directory after source-map conversion: -const thresholds: CoverageThreshold[] = [ - { pattern: /\/src\/core\//, minCoverage: 90 }, - { pattern: /\/src\/utils\//, minCoverage: 85 }, - { pattern: /\/src\/components\//, minCoverage: 70 }, - { pattern: /\/src\/pages\//, minCoverage: 60 }, -]; - -function checkThresholds(coverage: any[]): string[] { - const violations: string[] = []; - - for (const threshold of thresholds) { - const matchingFiles = coverage.filter((c) => threshold.pattern.test(c.url)); - - let total = 0; - let covered = 0; - - for (const file of matchingFiles) { - total += file.text?.length || 0; - covered += file.ranges.reduce( - (sum: number, r: any) => sum + (r.end - r.start), - 0 - ); - } - - const percent = total > 0 ? (covered / total) * 100 : 0; - - if (percent < threshold.minCoverage) { - violations.push( - `${threshold.pattern}: ${percent.toFixed(1)}% < ${ - threshold.minCoverage - }%` - ); - } - } - - return violations; -} +```bash +npx nyc check-coverage --temp-dir=.nyc_output --include='**/src/core/**' --per-file --lines=90 ``` +Ensure your app's expected source files appear in the report. Runtime coverage only includes loaded scripts; an empty or incomplete report is not proof that unvisited source files meet a threshold. + ## Advanced Patterns ### Merging Coverage Across Shards -```typescript -// scripts/merge-coverage.ts -import fs from "fs"; -import { glob } from "glob"; - -async function mergeCoverage() { - const files = await glob("shard-*/coverage/*.json"); - const merged = new Map(); - - for (const file of files) { - const data = JSON.parse(fs.readFileSync(file, "utf-8")); - for (const entry of data) { - if (merged.has(entry.url)) { - const existing = merged.get(entry.url); - existing.ranges.push(...entry.ranges); - } else { - merged.set(entry.url, { ...entry }); - } - } - } - - fs.writeFileSync( - "./coverage/merged.json", - JSON.stringify([...merged.values()]) - ); -} +Download each shard's `test-results/` into a separate directory, then pass all roots to the conversion script. `CoverageMap.merge()` combines execution counts for the same source file; concatenating raw ranges and adding lengths would double-count overlapping coverage. -mergeCoverage(); +```bash +npx tsx scripts/convert-coverage.ts shard-1/test-results shard-2/test-results +npx nyc report --temp-dir=.nyc_output --report-dir=coverage --reporter=html --reporter=lcov ``` ### Incremental Coverage -```typescript -// Check coverage only for changed files in CI -import { execSync } from "child_process"; -import fs from "fs"; - -const changedFiles = execSync("git diff --name-only HEAD~1") - .toString() - .split("\n") - .filter((f) => f.endsWith(".ts")); - -const coverage = JSON.parse(fs.readFileSync("./coverage/merged.json", "utf-8")); - -for (const file of changedFiles) { - const entry = coverage.find((c: any) => c.url.includes(file)); - if (entry) { - const percent = - (entry.ranges.reduce((s: number, r: any) => s + r.end - r.start, 0) / - (entry.text?.length || 1)) * - 100; - console.log(`${file}: ${percent.toFixed(1)}%`); - } -} -``` +Compare changed source paths with the source-mapped Istanbul report, not browser bundle URLs. Retain the full report as the baseline; an unloaded changed file must not silently count as covered. ## CI Integration ### GitHub Actions ```yaml -# .github/workflows/test.yml -name: Tests with Coverage - -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: 22 - - - run: npm ci - - run: npx playwright install --with-deps - - - name: Run tests with coverage - run: npm run test:coverage - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - files: ./coverage/lcov.info - fail_ci_if_error: true - - - name: Check coverage threshold - run: | - COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct') - if (( $(echo "$COVERAGE < 80" | bc -l) )); then - echo "Coverage $COVERAGE% is below 80% threshold" - exit 1 - fi +# Steps in an existing job, after checkout, npm ci and browser installation. +- name: Run tests with coverage fixture + run: npx playwright test --project=coverage +- name: Convert and report coverage + run: | + npx tsx scripts/convert-coverage.ts + npx nyc report --temp-dir=.nyc_output --report-dir=coverage --reporter=lcov --reporter=json-summary + npx nyc check-coverage --temp-dir=.nyc_output --lines=80 +- uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: coverage + path: coverage/ ``` ## Anti-Patterns to Avoid -| Anti-Pattern | Problem | Solution | -| ---------------------------- | -------------------------------------- | --------------------------- | -| Coverage for coverage's sake | Gaming metrics | Focus on critical paths | -| 100% coverage target | Diminishing returns, tests for getters | Set realistic thresholds | -| Ignoring coverage drops | Technical debt | Enforce thresholds in CI | -| No source map support | Wrong line numbers | Enable source maps in build | -| Coverage only in CI | Late feedback | Run locally too | +| Anti-Pattern | Problem | Solution | +| --- | --- | --- | +| Coverage for coverage's sake | Gaming metrics | Focus on critical paths | +| 100% coverage target | Diminishing returns | Set realistic thresholds | +| Ignoring coverage drops | Technical debt | Enforce thresholds in CI | +| No source map support | Wrong source locations | Enable source maps in build | +| Collecting on an unused page | Misses test activity | Use the test's page fixture | +| Mixing JS and CSS coverage schemas | Invalid calculations/conversion | Process each format separately | ## Related References diff --git a/external/develop/devops/playwright-best-practices/testing-patterns/accessibility.md b/external/develop/devops/playwright-best-practices/testing-patterns/accessibility.md index 1f13e21..032e441 100644 --- a/external/develop/devops/playwright-best-practices/testing-patterns/accessibility.md +++ b/external/develop/devops/playwright-best-practices/testing-patterns/accessibility.md @@ -186,6 +186,37 @@ test("escape closes modal", async ({ page }) => { }); ``` +### Page-Level Shortcuts + +Press the shortcut right after load, before any click. A click first gives the page keyboard focus and hides the case where nothing has focus yet. + +```typescript +test("shortcut works right after load", async ({ page }) => { + await page.goto("/review"); + await expect(page.getByRole("button", { name: "Reveal" })).toBeVisible(); + + await page.keyboard.press("Space"); + + await expect(page.getByTestId("answer")).toBeVisible(); +}); +``` + +A top-level Playwright page has keyboard focus when it loads. An app embedded in an iframe or an editor pane does not: key presses go to the host page until the user clicks into the app. To test that case, load the host page and press the key without clicking into the frame: + +```typescript +test("shortcut works when embedded", async ({ page }) => { + await page.goto("/embed-host"); // page that renders the app in an iframe + const app = page.frameLocator("#app-frame"); + await expect(app.getByRole("button", { name: "Reveal" })).toBeVisible(); + + await page.keyboard.press("Space"); + + // Fails if the app needs a click before it gets key events. + // Fix in the app, for example by focusing its root element on load. + await expect(app.getByTestId("answer")).toBeVisible(); +}); +``` + ## ARIA Validation ### Role Verification diff --git a/external/develop/devops/playwright-best-practices/testing-patterns/browser-extensions.md b/external/develop/devops/playwright-best-practices/testing-patterns/browser-extensions.md index f3991fb..82ea6d7 100644 --- a/external/develop/devops/playwright-best-practices/testing-patterns/browser-extensions.md +++ b/external/develop/devops/playwright-best-practices/testing-patterns/browser-extensions.md @@ -29,7 +29,7 @@ import path from "path"; export default defineConfig({ testDir: "./tests", use: { - // Extensions require non-headless Chromium + // Use headed Chromium, or channel: "chromium" for headless extensions. headless: false, }, projects: [ @@ -115,8 +115,9 @@ test("load MV3 extension", async () => { ], }); - // Wait for service worker - const serviceWorker = await context.waitForEvent("serviceworker"); + // The worker may already exist when launchPersistentContext returns. + const serviceWorker = context.serviceWorkers()[0] || + (await context.waitForEvent("serviceworker")); expect(serviceWorker.url()).toContain("chrome-extension://"); await context.close(); @@ -125,6 +126,8 @@ test("load MV3 extension", async () => { ### Manifest V2 (Background Page) +Legacy only: current bundled Chromium no longer supports Manifest V2. The following example requires an older compatible browser; use Manifest V3 with current Playwright. + ```typescript test("load MV2 extension", async () => { const pathToExtension = path.join(__dirname, "../my-extension-v2"); @@ -137,8 +140,9 @@ test("load MV2 extension", async () => { ], }); - // Wait for background page - const backgroundPage = await context.waitForEvent("backgroundpage"); + // The background page may already exist when launch returns. + const backgroundPage = context.backgroundPages()[0] || + (await context.waitForEvent("backgroundpage")); expect(backgroundPage.url()).toContain("chrome-extension://"); await context.close(); @@ -160,11 +164,8 @@ test("load multiple extensions", async () => { ], }); - // Both service workers should be available - await context.waitForEvent("serviceworker"); - await context.waitForEvent("serviceworker"); - - expect(context.serviceWorkers().length).toBe(2); + // Count existing workers too; waiting for two new events can miss startup. + await expect.poll(() => context.serviceWorkers().length).toBe(2); await context.close(); }); @@ -276,7 +277,8 @@ test("background script logic", async ({ context }) => { ```typescript test("alarm triggers correctly", async ({ context }) => { - const serviceWorker = await context.waitForEvent("serviceworker"); + const serviceWorker = context.serviceWorkers()[0] || + (await context.waitForEvent("serviceworker")); // Create alarm await serviceWorker.evaluate(async () => { @@ -370,7 +372,8 @@ test("content script modifies page", async ({ context }) => { ```typescript test("chrome.storage operations", async ({ context }) => { - const serviceWorker = await context.waitForEvent("serviceworker"); + const serviceWorker = context.serviceWorkers()[0] || + (await context.waitForEvent("serviceworker")); // Set storage await serviceWorker.evaluate(async () => { @@ -401,7 +404,8 @@ test("chrome.storage operations", async ({ context }) => { ```typescript test("chrome.tabs operations", async ({ context }) => { - const serviceWorker = await context.waitForEvent("serviceworker"); + const serviceWorker = context.serviceWorkers()[0] || + (await context.waitForEvent("serviceworker")); // Create a tab const page = await context.newPage(); @@ -425,37 +429,24 @@ test("chrome.tabs operations", async ({ context }) => { ### Context Menus ```typescript -test("context menu actions", async ({ context, extensionId }) => { - const serviceWorker = await context.waitForEvent("serviceworker"); - - // Create context menu - await serviceWorker.evaluate(async () => { - await chrome.contextMenus.create({ - id: "test-menu", - title: "Test Action", - contexts: ["selection"], - }); - }); - - // Simulate context menu click - const page = await context.newPage(); - await page.goto("https://example.com"); - - // Select text - await page.evaluate(() => { - const range = document.createRange(); - range.selectNodeContents(document.body.firstChild!); - window.getSelection()?.addRange(range); - }); +// Playwright cannot open Chrome's native context menu, and +// chrome.contextMenus.onClicked has no dispatch(). Test the handler that the +// extension registers with onClicked.addListener as a plain function. +import { test, expect } from "@playwright/test"; +import { onContextMenuClick } from "../src/background/contextMenu"; + +test("context menu action saves the selection", async () => { + const saved: Record[] = []; + globalThis.chrome = { + storage: { local: { set: async (items: Record) => { saved.push(items); } } }, + } as unknown as typeof chrome; + + await onContextMenuClick( + { menuItemId: "save-selection", selectionText: "selected text" } as chrome.contextMenus.OnClickData, + { id: 1, url: "https://example.com" } as chrome.tabs.Tab, + ); - // Trigger context menu action programmatically - await serviceWorker.evaluate(async () => { - // Simulate the click handler - chrome.contextMenus.onClicked.dispatch( - { menuItemId: "test-menu", selectionText: "selected text" }, - { id: 1, url: "https://example.com" }, - ); - }); + expect(saved).toEqual([{ savedSelection: "selected text" }]); }); ``` @@ -493,7 +484,7 @@ test("request permissions", async ({ context, extensionId }) => { | Anti-Pattern | Problem | Solution | | ------------------------------ | --------------------- | ---------------------------------------- | -| Testing in headless mode | Extensions don't load | Use `headless: false` | +| Using the default headless shell | Extensions don't load | Use headed mode or `channel: "chromium"` for headless extensions | | Not waiting for service worker | Race conditions | Wait for `serviceworker` event | | Hardcoding extension ID | ID changes on reload | Extract ID from service worker URL | | Testing packed extensions only | Slow iteration | Test unpacked during development | diff --git a/external/develop/devops/playwright-best-practices/testing-patterns/component-testing.md b/external/develop/devops/playwright-best-practices/testing-patterns/component-testing.md index 9cb36ae..f3b6ae2 100644 --- a/external/develop/devops/playwright-best-practices/testing-patterns/component-testing.md +++ b/external/develop/devops/playwright-best-practices/testing-patterns/component-testing.md @@ -230,7 +230,7 @@ test("click event fires", async ({ mount }) => { await component.click(); - expect(clicked).toBe(true); + await expect.poll(() => clicked).toBe(true); }); ``` @@ -250,7 +250,7 @@ test("onChange provides correct value", async ({ mount }) => { await component.getByRole("combobox").click(); await component.getByRole("option", { name: "b" }).click(); - expect(values).toEqual(["b"]); + await expect.poll(() => values).toEqual(["b"]); }); ``` @@ -272,7 +272,7 @@ test("form submission", async ({ mount }) => { await component.getByLabel("Password").fill("secret123"); await component.getByRole("button", { name: "Sign in" }).click(); - expect(submittedData).toEqual({ + await expect.poll(() => submittedData).toEqual({ email: "test@example.com", password: "secret123", }); @@ -338,14 +338,26 @@ test("renders named slots", async ({ mount }) => { ### Testing Render Props ```tsx -test("render prop pattern", async ({ mount }) => { - const component = await mount( +// DataFetcherStory.tsx — browser-side wrapper, next to the component. +export function DataFetcherStory() { + return ( {({ data, loading }) => loading ? Loading... : {data.name} } - , + ); +} +``` + +```tsx +// In the spec: +import { DataFetcherStory } from "./DataFetcherStory"; + +// Render props execute synchronously in the browser; a Node-side callback +// passed directly through mount cannot return JSX to the browser. +test("render prop pattern", async ({ mount }) => { + const component = await mount(); // Initially loading await expect(component.getByText("Loading...")).toBeVisible(); @@ -427,14 +439,12 @@ test("with mocked hook", async ({ mount }) => { ### React Testing ```tsx -// React with refs -test("exposes ref methods", async ({ mount }) => { - let inputRef: HTMLInputElement | null = null; - - const component = await mount( (inputRef = el)} />); - +// DOM nodes cannot be passed from the browser to a Node-side ref callback. +// Assert rendered input state through a locator instead. +test("input value", async ({ mount }) => { + const component = await mount(); await component.locator("input").fill("test"); - expect(inputRef?.value).toBe("test"); + await expect(component.locator("input")).toHaveValue("test"); }); // React with context @@ -466,7 +476,7 @@ test("v-model binding", async ({ mount }) => { }); await component.locator("input").fill("test"); - expect(modelValue).toBe("test"); + await expect.poll(() => modelValue).toBe("test"); }); ``` diff --git a/external/develop/devops/playwright-best-practices/testing-patterns/drag-drop.md b/external/develop/devops/playwright-best-practices/testing-patterns/drag-drop.md index 4d4d0ab..e7fe14c 100644 --- a/external/develop/devops/playwright-best-practices/testing-patterns/drag-drop.md +++ b/external/develop/devops/playwright-best-practices/testing-patterns/drag-drop.md @@ -295,9 +295,12 @@ test('simulates drag-over visual feedback', async ({ page }) => { const dropZone = page.locator('[data-testid="file-drop-zone"]'); - await dropZone.dispatchEvent('dragenter', { - dataTransfer: { types: ['Files'] }, + const dataTransfer = await page.evaluateHandle(() => { + const transfer = new DataTransfer(); + transfer.items.add(new File(['content'], 'report.pdf', { type: 'application/pdf' })); + return transfer; }); + await dropZone.dispatchEvent('dragenter', { dataTransfer }); await expect(dropZone).toHaveClass(/drag-active|drop-highlight/); await expect(dropZone).toContainText(/drop.*here|release.*upload/i); diff --git a/external/develop/devops/playwright-best-practices/testing-patterns/file-upload-download.md b/external/develop/devops/playwright-best-practices/testing-patterns/file-upload-download.md index 75a4348..43996e4 100644 --- a/external/develop/devops/playwright-best-practices/testing-patterns/file-upload-download.md +++ b/external/develop/devops/playwright-best-practices/testing-patterns/file-upload-download.md @@ -241,7 +241,7 @@ test('removes one file from selection', async ({ page }) => { ## Drag-and-Drop Zones -Drop zones always have an underlying `input[type="file"]`—target it directly instead of simulating OS-level drag events. +When a drop zone has an underlying `input[type="file"]`, target it directly for file selection. To exercise a drop-event handler itself, dispatch a `drop` event with a real `DataTransfer` handle (see [file-operations.md](file-operations.md#drag-and-drop)). ```typescript test('uploads via drop zone', async ({ page }) => { @@ -267,9 +267,12 @@ test('shows visual feedback on drag-over', async ({ page }) => { const dropZone = page.locator('[data-testid="drop-zone"]'); - await dropZone.dispatchEvent('dragenter', { - dataTransfer: { types: ['Files'], files: [] }, + const dataTransfer = await page.evaluateHandle(() => { + const transfer = new DataTransfer(); + transfer.items.add(new File(['content'], 'dropped.pdf', { type: 'application/pdf' })); + return transfer; }); + await dropZone.dispatchEvent('dragenter', { dataTransfer }); await expect(dropZone).toHaveClass(/active|highlight|drag-over/); await expect(dropZone).toContainText(/release|drop now/i); @@ -551,7 +554,7 @@ test('downloads file requiring authentication', async ({ page, request }) => { ## Tips -1. **Use `setInputFiles` for uploads**. Even drag-and-drop zones have an underlying `input[type="file"]`. Target it directly instead of simulating OS-level drag events. +1. **Use `setInputFiles` for file inputs**. For a drop zone with an underlying `input[type="file"]`, target the input to select files. This does not exercise the zone's drag/drop handlers; use a real `DataTransfer` handle when testing those handlers. 2. **Prefer in-memory buffers**. Creating files with `Buffer.from()` keeps tests self-contained. Use fixture files only when you need real content (e.g., a valid PDF your app parses). diff --git a/external/develop/devops/playwright-best-practices/testing-patterns/forms-validation.md b/external/develop/devops/playwright-best-practices/testing-patterns/forms-validation.md index 2cfdd8c..69e1bdd 100644 --- a/external/develop/devops/playwright-best-practices/testing-patterns/forms-validation.md +++ b/external/develop/devops/playwright-best-practices/testing-patterns/forms-validation.md @@ -511,22 +511,18 @@ test("confirmation dialog before resetting a dirty form", async ({ page }) => { ### `fill()` does nothing or clears but doesn't type -**Cause**: The input field uses a contenteditable div (rich text editors), not a real `` or ` +
+ + Ledger data + +
+ +
    +
  • +
  • +
+ diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-prompt/component.js b/external/develop/frontend/design-taste-frontend/assets/components/fx-prompt/component.js new file mode 100644 index 0000000..6441907 --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-prompt/component.js @@ -0,0 +1,24 @@ +// fx-prompt: edge-glow draws the prompt card itself, with one light lapping its border about every 12 s; +// a pause button at the end of the examples row holds it +import { mount, palette, bag, tone, pauser } from '../shared.js'; + +export default function init(root) { + const b = bag(); + const box = root.querySelector('.cx-prompt__box'); + const input = root.querySelector('.cx-prompt__input'); + const page = palette(root); + const inset = parseFloat(getComputedStyle(root).getPropertyValue('--inset')) || 20; + const radius = parseFloat(getComputedStyle(root).getPropertyValue('--r-lg')) || 16; + const p = b.add(pauser(root, { label: 'the light around the field' })); + b.add(p.add(mount(box, 'edge-glow', { + palette: { ...page, surface: tone(page, 0.035) }, + seed: root.dataset.cxSeed, + params: { inset, radius, width: 1.2, glow: 22, bloom: 0.55, segment: 0.38, speed: 110, idle: 0.32, wash: 0.4, fill: 1 }, + }))); + root.querySelectorAll('.cx-prompt__hints button').forEach(h => { + const f = () => { input.value = h.textContent; input.focus(); }; + h.addEventListener('click', f); + b.add(() => h.removeEventListener('click', f)); + }); + return b; +} diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-prompt/meta.json b/external/develop/frontend/design-taste-frontend/assets/components/fx-prompt/meta.json new file mode 100644 index 0000000..b5b0a27 --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-prompt/meta.json @@ -0,0 +1,10 @@ +{ + "id": "fx-prompt", + "name": "Prompt field with edge glow", + "for": "The hero prompt box of an AI or developer product: one light runs slowly around its border and blooms outward, so the one input on the page is found first.", + "effects": ["edge-glow"], + "default": "edge-glow", + "how": "edge-glow mounted on the box with an inset: it draws the card and its border, and the glow spreads into the margin around it. One lap takes about 12 s.", + "rules": ["One lit border per screen; other boxes stay static.", "A lap takes 6 to 18 s, never faster.", "The text area and buttons sit on the opaque card fill."], + "a11y": "A real form with a labelled textarea and labelled buttons; the canvas is aria-hidden. Reduced motion shows the border still, with its faint idle light." +} diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-search/component.css b/external/develop/frontend/design-taste-frontend/assets/components/fx-search/component.css new file mode 100644 index 0000000..d20aaac --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-search/component.css @@ -0,0 +1,38 @@ +/* fx-search: a search field that gathers a soft ambient glow of the effect around it while focused */ +.cx-search { position: relative; isolation: isolate; width: min(100%, 520px); margin-inline: auto; } +.cx-search__glow { + position: absolute; inset: -22px -30px; z-index: -1; border-radius: 40px; pointer-events: none; + filter: blur(22px); opacity: 0; transform: scale(.96); + -webkit-mask: radial-gradient(closest-side, #000 55%, transparent); mask: radial-gradient(closest-side, #000 55%, transparent); + transition: opacity .6s var(--cx-ease), transform .6s var(--cx-ease); +} +.cx-search.is-focus .cx-search__glow { opacity: .6; transform: none; } +.cx-search__field { + position: relative; display: flex; align-items: center; gap: 10px; height: 52px; padding: 0 12px 0 16px; + border-radius: var(--r-md); background: var(--bg); border: 1px solid var(--cx-line-2); + box-shadow: 0 1px 2px rgb(0 0 0 / .06); + transition: border-color .3s var(--cx-ease), box-shadow .3s var(--cx-ease); +} +.cx-search.is-focus .cx-search__field { border-color: color-mix(in oklab, var(--accent) 55%, var(--cx-line-2)); box-shadow: 0 8px 28px -10px rgb(0 0 0 / .4); } +.cx-search__icon { color: var(--muted); flex: none; } +.cx-search__input { + flex: 1; min-width: 0; height: 100%; border: 0; outline: 0; background: transparent; padding: 0; + font: 400 var(--t-body)/1 var(--font-text); color: var(--ink); +} +.cx-search__input::placeholder { color: var(--muted); } +.cx-search__input::-webkit-search-cancel-button { display: none; } +.cx-search__list { + position: absolute; left: 0; right: 0; top: calc(100% + 8px); z-index: 5; margin: 0; padding: 4px; list-style: none; + border-radius: var(--r-md); background: var(--bg); box-shadow: var(--cx-shadow); +} +.cx-search__list li { + display: flex; align-items: center; gap: 12px; height: 40px; padding: 0 12px; border-radius: var(--r-sm); + font-size: var(--t-small); color: var(--ink); cursor: pointer; +} +.cx-search__list li[aria-selected="true"], .cx-search__list li:hover { background: var(--cx-panel); } +/* the rule above sets display, so the hidden attribute needs its own rule or filtered rows stay on screen */ +.cx-search__list[hidden], .cx-search__list li[hidden], .cx-search__empty[hidden] { display: none; } +.cx-search__kind { width: 56px; font: 400 11px/1 var(--cx-mono); color: var(--muted); } +.cx-search__empty { position: absolute; top: calc(100% + 8px); left: 0; right: 0; margin: 0; padding: 14px 16px; border-radius: var(--r-md); background: var(--bg); box-shadow: var(--cx-shadow); font-size: var(--t-small); color: var(--muted); } +/* the stage leaves room for the open list */ +.cx-search { margin-bottom: 0; } diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-search/component.html b/external/develop/frontend/design-taste-frontend/assets/components/fx-search/component.html new file mode 100644 index 0000000..7b17466 --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-search/component.html @@ -0,0 +1,17 @@ + diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-search/component.js b/external/develop/frontend/design-taste-frontend/assets/components/fx-search/component.js new file mode 100644 index 0000000..0cda8d8 --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-search/component.js @@ -0,0 +1,104 @@ +// fx-search: the glow is an effect host behind the field, blurred into ambient light; it fades in and +// runs only while the field has focus. At rest it holds no WebGL context: the effect is mounted on the +// first focus and removed (its context freed) a few seconds after the field loses focus. The +// suggestions filter as you type and work with the keyboard. +import { mount, palette, color, bag, tuned } from '../shared.js'; + +// each effect this part takes, tuned on its own: one effect's params mean something else to another +const PARAMS = { + 'aurora-curtains': { speed: 0.12, bands: 3, height: 0.8, intensity: 1.6, stars: 0, calm: 0 }, + 'grain-gradient': { speed: 0.12, calm: 0 }, + 'gradient-mesh': { speed: 0.12, lines: 0, warp: 0.7, softness: 0.55, tint: 1, calm: 0 }, + 'soft-light-bars': { speed: 0.4, softness: 0.7, glow: 1, haze: 0.5, stipple: 0.2, grain: 0.3, calm: 0 }, +}; + +export default function init(root) { + const b = bag(); + const glow = root.querySelector('.cx-search__glow'); + const input = root.querySelector('.cx-search__input'); + const list = root.querySelector('.cx-search__list'); + const empty = root.querySelector('.cx-search__empty'); + const items = [...list.querySelectorAll('[role="option"]')]; + const page = palette(root); + let ctrl = null, pending = null, dead = false, timer = 0, freeT = 0, active = -1; + const ensure = () => { + if (ctrl || pending) return; + const effect = root.dataset.cxEffect || (color.isDark(page.bg) ? 'aurora-curtains' : 'grain-gradient'); + pending = mount(glow, effect, { + // the effect's own ground is already an accent tint, so the blurred light reaches all round the field + palette: { ...page, bg: color.mix(page.bg, page.accent, color.isDark(page.bg) ? 0.38 : 0.3), accent: color.isDark(page.bg) ? page.accent : color.saturate(page.accent, 1.1) }, + seed: root.dataset.cxSeed, + params: tuned(PARAMS, effect, { speed: 0.12, calm: 0 }), + }).then(c => { + pending = null; + if (dead) { c.destroy(); return; } + ctrl = c; + if (!root.classList.contains('is-focus')) c.pause(); + }, err => { pending = null; console.warn('fx-search:', err); }); + }; + const free = () => { ctrl?.destroy(); ctrl = null; }; + b.add(() => { dead = true; clearTimeout(freeT); free(); }); + + const visible = () => items.filter(li => !li.hidden); + const select = n => { + const v = visible(); + items.forEach(li => li.setAttribute('aria-selected', 'false')); + if (!v.length) { active = -1; input.removeAttribute('aria-activedescendant'); return; } + active = (n + v.length) % v.length; + v[active].setAttribute('aria-selected', 'true'); + input.setAttribute('aria-activedescendant', v[active].id); + }; + const open = yes => { + const v = visible(); + list.hidden = !yes || !v.length; + empty.hidden = !yes || !!v.length; + input.setAttribute('aria-expanded', String(yes && !!v.length)); + }; + const filter = () => { + const q = input.value.trim().toLowerCase(); + items.forEach(li => { li.hidden = !!q && !li.textContent.toLowerCase().includes(q); }); + select(0); + open(true); + }; + const focus = () => { clearTimeout(timer); clearTimeout(freeT); root.classList.add('is-focus'); ensure(); ctrl?.resume(); filter(); }; + const blur = () => { + root.classList.remove('is-focus'); + open(false); + timer = setTimeout(() => ctrl?.pause(), 700); + freeT = setTimeout(free, 4000); + }; + const key = e => { + if (e.key === 'ArrowDown') { e.preventDefault(); select(active + 1); } + else if (e.key === 'ArrowUp') { e.preventDefault(); select(active - 1); } + else if (e.key === 'Enter' && active >= 0) { e.preventDefault(); input.value = visible()[active].lastChild.textContent.trim(); filter(); } + else if (e.key === 'Escape') { input.value = ''; input.blur(); } + }; + const pick = e => { + const li = e.target.closest('[role="option"]'); + if (!li) return; + e.preventDefault(); + input.value = li.lastChild.textContent.trim(); + filter(); + }; + // "/" focuses the field from anywhere on the page (the kbd hint shows it) + const slash = e => { + if (e.key !== '/' || e.defaultPrevented || /input|textarea|select/i.test(document.activeElement?.tagName || '') || document.activeElement?.isContentEditable) return; + const r = root.getBoundingClientRect(); + if (r.bottom < 0 || r.top > innerHeight) return; + e.preventDefault(); + input.focus(); + }; + input.addEventListener('focus', focus); + input.addEventListener('blur', blur); + input.addEventListener('input', filter); + input.addEventListener('keydown', key); + list.addEventListener('mousedown', pick); + document.addEventListener('keydown', slash); + b.add(() => { + clearTimeout(timer); + input.removeEventListener('focus', focus); input.removeEventListener('blur', blur); + input.removeEventListener('input', filter); input.removeEventListener('keydown', key); + list.removeEventListener('mousedown', pick); document.removeEventListener('keydown', slash); + }); + return b; +} diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-search/meta.json b/external/develop/frontend/design-taste-frontend/assets/components/fx-search/meta.json new file mode 100644 index 0000000..39cd030 --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-search/meta.json @@ -0,0 +1,10 @@ +{ + "id": "fx-search", + "name": "Search with ambient glow", + "for": "A docs or command search field that gathers a soft light of the effect around it while it has focus, so the place you are typing is obvious without a loud ring.", + "effects": ["aurora-curtains", "grain-gradient", "gradient-mesh", "soft-light-bars"], + "default": "aurora-curtains", + "how": "aurora-curtains on a dark ground, grain-gradient on a light one, in a host behind the field, 22-30px larger on every side, blurred into ambient light; it fades in on focus and runs only then. A real combobox with a filtered list.", + "rules": ["The glow tints at most a few percent where it meets the field; text never sits on it.", "Glow only on focus, never at rest.", "One glowing field per page."], + "a11y": "An input with role combobox, a labelled listbox, aria-activedescendant for arrow keys, Enter to choose, Escape to clear; / focuses it. Reduced motion keeps the glow still." +} diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-spotlight/component.css b/external/develop/frontend/design-taste-frontend/assets/components/fx-spotlight/component.css new file mode 100644 index 0000000..23cbecb --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-spotlight/component.css @@ -0,0 +1,31 @@ +/* fx-spotlight: plain cards with a faint dot ruling in their empty corner; a small soft light under the pointer brings it up */ +@property --cx-spot-r { syntax: ""; inherits: true; initial-value: 0px; } +.cx-spot { + --cx-spot-r: 0px; --mx: 50%; --my: 50%; + position: relative; isolation: isolate; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-4); + transition: --cx-spot-r .45s var(--cx-ease); +} +.cx-spot.is-on { --cx-spot-r: 150px; } +.cx-spot__card { + position: relative; display: flex; flex-direction: column; gap: var(--s-6); min-height: 300px; padding: var(--s-6); + border-radius: var(--r-lg); background: var(--cx-card); border: 1px solid var(--cx-line); box-shadow: inset 0 1px 0 var(--cx-hi); +} +.cx-spot__card > * { position: relative; z-index: 2; } +.cx-spot__icon { + display: grid; place-items: center; width: 40px; height: 40px; border-radius: var(--r-md); + background: var(--bg); border: 1px solid var(--cx-line-2); color: var(--ink); +} +.cx-spot__icon svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; } +.cx-spot__body { display: grid; gap: var(--s-2); margin-top: auto; } +.cx-spot__link { + font-size: var(--t-small); font-weight: 500; color: var(--ink); display: inline-flex; gap: 6px; align-items: center; width: fit-content; +} +.cx-spot__link::after { content: "\2192"; transition: transform var(--dur) var(--ease); } +.cx-spot__link:hover::after { transform: translateX(2px); } +.cx-spot__card { cursor: pointer; } +.cx-spot__card:has(.cx-spot__link:focus-visible) { outline: 2px solid var(--action); outline-offset: 2px; } +.cx-spot__fx { position: absolute; inset: 0; z-index: 1; pointer-events: none; } +@media (max-width: 899px) { + .cx-spot { grid-template-columns: minmax(0, 1fr); } + .cx-spot__card { min-height: 220px; } +} diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-spotlight/component.html b/external/develop/frontend/design-taste-frontend/assets/components/fx-spotlight/component.html new file mode 100644 index 0000000..d24ccaa --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-spotlight/component.html @@ -0,0 +1,27 @@ +
+
+ +
+

Single sign-on

+

Connect Okta, Entra ID or Google Workspace. People sign in with the account they already have.

+
+ Set up SSO for your team +
+
+ +
+

Audit log

+

Every change is recorded with who made it and when. Export the log at any time.

+
+ Read about the log +
+
+ +
+

Roles

+

Give each person exactly the access they need, project by project.

+
+ See the roles +
+ +
diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-spotlight/component.js b/external/develop/frontend/design-taste-frontend/assets/components/fx-spotlight/component.js new file mode 100644 index 0000000..4dd0ff1 --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-spotlight/component.js @@ -0,0 +1,80 @@ +// fx-spotlight: one effect canvas over the card row, a fine dot ruling at a few percent of the ink. Each +// card shows it only in its empty top-right corner (a mask that fades out long before the icon and the +// text), faintly at rest; a small soft light under the pointer brings that corner up. Without a fine +// pointer (touch) the corner simply rests at its faint level. The ruling is still: drawn once and kept +// as a picture (no WebGL context at rest); only the CSS mask follows the pointer. +import { still, palette, tone, color, windows, roundRectFade, smoother, hoverable, reduced, bag } from '../shared.js'; + +export default function init(root) { + const b = bag(); + const host = root.querySelector('.cx-spot__fx'); + const cards = [...root.querySelectorAll('.cx-spot__card')]; + const page = palette(root); + const live = hoverable(); + // each card: the ruling only in its far corner, gone by about two thirds of the way to the text + const shape = (w, h, r) => roundRectFade(w, h, r, { cx: 1, cy: 0, reach: 0.62, peak: 1 }); + // the pointer light: small and soft; at rest the corner keeps a third of its strength + const spot = 'radial-gradient(circle max(1px, var(--cx-spot-r)) at var(--mx) var(--my), #000 0%, rgb(0 0 0 / .6) 45%, rgb(0 0 0 / var(--rest, .34)) 100%)'; + const rest = 'linear-gradient(rgb(0 0 0 / .34), rgb(0 0 0 / .34))'; + b.add(windows(host, cards, { shape, extra: () => [{ image: live ? spot : rest }] })); + + // the whole card opens its link, unless the visitor is selecting text + const click = e => { + const card = e.target.closest('.cx-spot__card'); + if (!card || e.target.closest('a') || String(getSelection())) return; + card.querySelector('.cx-spot__link')?.click(); + }; + root.addEventListener('click', click); + b.add(() => root.removeEventListener('click', click)); + + const card = tone(page, 0.035); + const effect = root.dataset.cxEffect || 'ruled-grounds'; + const PARAMS = { + 'ruled-grounds': { cell: 14, major: 4, ink: 0.5, focus: 0, speed: 0, reach: 1, grain: 0, calm: 0 }, + 'contour-lines': { levels: 18, scale: 1.4, lineWidth: 1, indexEvery: 0, opacity: 0.4, speed: 0, tint: 0, shade: 0, edge: 0, calm: 0 }, + }; + b.add(still(host, effect, { + // the ground is the card itself; the marks are a quiet step of the ink + palette: { ...page, bg: card, surface: card, ink: color.mix(card, page.ink, 0.42), accent: color.mix(card, page.accent, 0.5) }, + seed: root.dataset.cxSeed, + params: PARAMS[effect] || { speed: 0, calm: 0 }, + })); + if (!live) return b; + + // the light trails the pointer a little (or jumps there under reduced motion) + const cur = [0, 0], target = [0, 0]; + let first = true; + const sm = smoother(dt => { + const k = reduced() ? 1 : 1 - Math.exp(-dt * 10); + cur[0] += (target[0] - cur[0]) * k; + cur[1] += (target[1] - cur[1]) * k; + root.style.setProperty('--mx', `${cur[0].toFixed(1)}px`); + root.style.setProperty('--my', `${cur[1].toFixed(1)}px`); + return Math.abs(target[0] - cur[0]) + Math.abs(target[1] - cur[1]) > 0.3; + }); + const at = (x, y) => { + const r = root.getBoundingClientRect(); + target[0] = x - r.left; target[1] = y - r.top; + if (first) { cur[0] = target[0]; cur[1] = target[1]; first = false; } + sm.kick(); + }; + const on = () => root.classList.add('is-on'); + const off = () => { root.classList.remove('is-on'); first = true; }; + const move = e => { if (e.target.closest('.cx-spot__card')) { on(); at(e.clientX, e.clientY); } else off(); }; + const focus = e => { + const c = e.target.closest('.cx-spot__card'); + if (!c) return; + const r = c.getBoundingClientRect(); + on(); first = true; at(r.right - r.width * 0.18, r.top + r.height * 0.2); + }; + root.addEventListener('pointermove', move); + root.addEventListener('pointerleave', off); + root.addEventListener('focusin', focus); + root.addEventListener('focusout', off); + b.add(() => { + sm.stop(); + root.removeEventListener('pointermove', move); root.removeEventListener('pointerleave', off); + root.removeEventListener('focusin', focus); root.removeEventListener('focusout', off); + }); + return b; +} diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-spotlight/meta.json b/external/develop/frontend/design-taste-frontend/assets/components/fx-spotlight/meta.json new file mode 100644 index 0000000..6bc1a8a --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-spotlight/meta.json @@ -0,0 +1,10 @@ +{ + "id": "fx-spotlight", + "name": "Spotlight cards", + "for": "A row of plain feature cards (security, admin, settings): a fine ruling sits faintly in each card's empty top-right corner, and a small soft light under the pointer brings that corner up; the cards themselves never move.", + "effects": ["ruled-grounds", "contour-lines"], + "default": "ruled-grounds", + "how": "One still effect host over the row, masked per card by a fade from the top-right corner (roundRectFade) that is gone well before the icon and the text, and by a 150px radial light at the pointer. The marks are a quiet step of the ink on the card colour.", + "rules": ["The only pointer-following element on the page.", "The effect never reaches the icon, the title or the body text.", "Pattern effects only (rulings, contours) at a few percent of the ink: never colour blobs, never a mesh."], + "a11y": "Each card is an article with a heading and one real link that covers the card. Keyboard focus lights the focused card's corner. The effect is still; reduced motion moves the light without easing." +} diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-stat/component.css b/external/develop/frontend/design-taste-frontend/assets/components/fx-stat/component.css new file mode 100644 index 0000000..2e80b03 --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-stat/component.css @@ -0,0 +1,22 @@ +/* fx-stat: big numbers on hairline rules; the digits carry a still effect on dark grounds, an ink fade on light ones */ +/* sized by its own width (a container), not the window: in a narrow column at 1440 three numbers sized + to the window ran into each other */ +.cx-stat { display: grid; gap: var(--s-4); container-type: inline-size; } +.cx-stat__row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-6); margin: 0; } +.cx-stat__item { display: flex; flex-direction: column-reverse; justify-content: flex-end; gap: var(--s-3); border-top: 1px solid var(--cx-line-2); padding-top: var(--s-5); min-width: 0; } +.cx-stat__num { + margin: 0; font-family: var(--font-display); font-weight: 600; letter-spacing: -0.045em; line-height: .95; + font-size: clamp(48px, 7.4cqi, 104px); font-variant-numeric: tabular-nums; white-space: nowrap; +} +/* light grounds: ink at the top fading to about 60% ink at the foot of the digits */ +.cx-stat--fade .cx-stat__num { + background: linear-gradient(180deg, var(--ink) 20%, color-mix(in oklab, var(--ink) 60%, var(--bg)) 95%); + -webkit-background-clip: text; background-clip: text; color: transparent; +} +.cx-stat__label { font-size: var(--t-small); line-height: 1.5; color: var(--muted); max-width: 26ch; } +.cx-stat__note { font-size: var(--t-label); color: var(--muted); margin: 0; } +@media (forced-colors: active) { .cx-stat--fade .cx-stat__num { color: CanvasText; background: none; } } +@container (max-width: 699px) { + .cx-stat__row { grid-template-columns: minmax(0, 1fr); gap: var(--s-5); } + .cx-stat__num { font-size: clamp(48px, 15cqi, 72px); } +} diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-stat/component.html b/external/develop/frontend/design-taste-frontend/assets/components/fx-stat/component.html new file mode 100644 index 0000000..72cc643 --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-stat/component.html @@ -0,0 +1,17 @@ +
+
+
+
Uptime over the last 12 months
+
99.99%
+
+
+
Median response time, worldwide
+
38 ms
+
+
+
Requests served each day
+
1.2B
+
+
+

From the public status page, November 2025 to October 2026.

+
diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-stat/component.js b/external/develop/frontend/design-taste-frontend/assets/components/fx-stat/component.js new file mode 100644 index 0000000..15158cd --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-stat/component.js @@ -0,0 +1,24 @@ +// fx-stat: on a dark or colour ground, one still effect canvas over the row of numbers, so only the +// digits show it (soft curtains of light, no motion), drawn once and kept as a picture (no WebGL context +// and no frames at rest). On a light ground an effect inside digits reads as scratched metal, so the +// digits get a plain ink fade instead: full ink at the top to about 60% ink. +import { fill, bag, paramsOf, palette, isDark, tuned } from '../shared.js'; + +// each effect this part takes, tuned on its own: one effect's params mean something else to another +const PARAMS = { + 'aurora-curtains': { bands: 4, height: 0.85, intensity: 1.3, calm: 0 }, + 'grain-gradient': { grainMotion: 0, calm: 0 }, +}; + +export default function init(root) { + const b = bag(); + const light = !isDark(palette(root)); + root.classList.toggle('cx-stat--fade', light); + b.add(() => root.classList.remove('cx-stat--fade')); + if (light) return b; + root.querySelectorAll('[data-cx-fill]').forEach(el => { + const params = { ...tuned(PARAMS, el.dataset.cxFill), ...(paramsOf(el) || {}), speed: 0 }; + b.add(fill(el, el.dataset.cxFill, { still: true, seed: el.dataset.cxSeed, params })); + }); + return b; +} diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-stat/meta.json b/external/develop/frontend/design-taste-frontend/assets/components/fx-stat/meta.json new file mode 100644 index 0000000..c4c0edd --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-stat/meta.json @@ -0,0 +1,10 @@ +{ + "id": "fx-stat", + "name": "Effect stat numbers", + "for": "A band of two to four proof numbers (uptime, speed, scale) where the big digits carry the effect and the captions stay plain.", + "effects": ["aurora-curtains", "grain-gradient"], + "default": "aurora-curtains", + "how": "Dark or colour ground: fill() on the row, one still canvas for all numbers, blended so only the digits show it. Light ground: no effect; the digits fade from ink to about 60% ink, because an effect inside digits on white reads as scratched metal.", + "rules": ["56px and up, weight 600; never small numbers.", "The effect in the digits is still (speed 0).", "Real figures. Invented ones are placeholders covered by the page's one placeholder note and listed in the report, never a sample tag on each number.", "The note under the row says where the figures come from.", "No count-up on scroll: the final number shows at once."], + "a11y": "A description list: each number is real text with its caption as the term; the overlay sits beside the list, not in it. Forced colours show plain digits." +} diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-tabs/component.css b/external/develop/frontend/design-taste-frontend/assets/components/fx-tabs/component.css new file mode 100644 index 0000000..41809c4 --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-tabs/component.css @@ -0,0 +1,64 @@ +/* fx-tabs: a tab bar whose active indicator is a small window of a still grain, gliding to the chosen tab */ +.cx-tabs { display: grid; gap: var(--s-5); max-width: 760px; margin-inline: auto; min-width: 0; } +.cx-tabs__scroll { overflow-x: auto; scrollbar-width: none; justify-self: center; max-width: 100%; padding: 2px; } +.cx-tabs__scroll::-webkit-scrollbar { display: none; } +.cx-tabs__bar { + position: relative; isolation: isolate; display: inline-flex; gap: 2px; padding: 4px; border-radius: 999px; + background: var(--cx-card); border: 1px solid var(--cx-line); box-shadow: inset 0 1px 0 var(--cx-hi); +} +/* the effect spans the whole bar and stays put; the pill is a window that slides over it (no canvas resize) */ +.cx-tabs__ind { + position: absolute; left: 0; right: 0; top: 4px; bottom: 4px; z-index: 0; background: var(--accent); + clip-path: inset(0 calc(100% - var(--x, 4px) - var(--w, 0px)) 0 var(--x, 4px) round 999px); + transition: clip-path .42s cubic-bezier(.3, .9, .3, 1); +} +.cx-tabs__tab { + position: relative; z-index: 1; height: 36px; padding: 0 16px; border: 0; border-radius: 999px; background: transparent; + font: 500 var(--t-small)/1 var(--font-text); color: var(--muted); cursor: pointer; white-space: nowrap; + transition: color .3s var(--cx-ease); +} +.cx-tabs__tab:hover { color: var(--ink); } +.cx-tabs__tab[aria-selected="true"] { color: var(--ink); } +/* white-on-accent only once the script has placed the pill (it writes --w inline) */ +.cx-tabs__bar:has(.cx-tabs__ind[style*="--w"]) .cx-tabs__tab[aria-selected="true"] { color: var(--accent-ink); } +.cx-tabs__tab:focus-visible { outline-offset: -2px; } +.cx-tabs__panel { + border-radius: var(--r-lg); background: var(--cx-card); border: 1px solid var(--cx-line); padding: var(--s-6); + box-shadow: inset 0 1px 0 var(--cx-hi); animation: cx-tabs-in .35s var(--cx-ease); +} +@keyframes cx-tabs-in { from { opacity: 0; transform: translateY(4px); } } +.cx-tabs__metrics { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-5); } +.cx-tabs__metrics div, .cx-tabs__payout > div { display: grid; gap: 6px; align-content: start; } +.cx-tabs__metrics span, .cx-tabs__payout span { font-size: var(--t-label); color: var(--muted); } +.cx-tabs__metrics b, .cx-tabs__payout b { font: 560 28px/1.1 var(--font-display); letter-spacing: -0.025em; } +.cx-tabs__metrics em { font-style: normal; font-size: var(--t-label); color: color-mix(in oklab, var(--accent) 75%, var(--ink)); } +.cx-tabs__table { width: 100%; border-collapse: collapse; font-size: var(--t-small); } +.cx-tabs__table th { text-align: left; font-weight: 400; font-size: var(--t-label); color: var(--muted); padding: 0 0 10px; } +.cx-tabs__table td { padding: 12px 0; border-top: 1px solid var(--cx-line); } +.cx-tabs__table td:last-child, .cx-tabs__table th:last-child { text-align: right; color: var(--muted); } +.cx-tabs__chip { display: inline-flex; align-items: center; height: 22px; padding: 0 8px; border-radius: var(--r-sm); font-size: 12px; background: var(--cx-panel); color: var(--muted); } +.cx-tabs__chip.is-ok { color: var(--ink); background: color-mix(in oklab, var(--accent) 18%, var(--cx-card)); } +.cx-tabs__payout { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-6); } +.cx-tabs__payout p { margin: 4px 0 0; font-size: var(--t-small); color: var(--muted); } +.cx-tabs__payout ol { margin: 0; padding: 0; list-style: none; display: grid; } +.cx-tabs__payout li { display: flex; justify-content: space-between; padding: 10px 0; border-top: 1px solid var(--cx-line); font-size: var(--t-small); } +.cx-tabs__payout li b { font: 500 var(--t-small)/1.4 var(--font-text); letter-spacing: 0; } +.cx-tabs__reports { margin: 0; padding: 0; list-style: none; } +.cx-tabs__reports li { display: flex; justify-content: space-between; align-items: center; gap: var(--s-4); padding: 12px 0; border-top: 1px solid var(--cx-line); font-size: var(--t-small); } +.cx-tabs__reports li:first-child { border-top: 0; padding-top: 0; } +.cx-tabs__reports a { font: 500 12px/1 var(--cx-mono); height: 26px; display: inline-flex; align-items: center; padding: 0 10px; border-radius: var(--r-sm); border: 1px solid var(--cx-line-2); } +@media (prefers-reduced-motion: reduce) { .cx-tabs__ind { transition: none; } .cx-tabs__panel { animation: none; } } +@media (max-width: 639px) { + .cx-tabs__panel { padding: var(--s-5); } + /* a narrow screen scrolls the bar sideways; the tabs keep 14px padding and the far edge fades */ + .cx-tabs__scroll { + justify-self: stretch; + -webkit-mask: linear-gradient(90deg, #000 calc(100% - 28px), transparent); mask: linear-gradient(90deg, #000 calc(100% - 28px), transparent); + } + .cx-tabs__scroll { text-align: center; } + .cx-tabs__scroll.is-end { -webkit-mask: none; mask: none; } + .cx-tabs__tab { padding: 0 14px; font-size: 13px; } + .cx-tabs__metrics, .cx-tabs__payout { grid-template-columns: minmax(0, 1fr); gap: var(--s-4); } + .cx-tabs__table th:nth-child(4), .cx-tabs__table td:nth-child(4) { display: none; } + .cx-tabs__table td:nth-child(3), .cx-tabs__table th:nth-child(3) { text-align: right; } +} diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-tabs/component.html b/external/develop/frontend/design-taste-frontend/assets/components/fx-tabs/component.html new file mode 100644 index 0000000..f7bf354 --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-tabs/component.html @@ -0,0 +1,46 @@ +
+
+
+ + + + + +
+
+ +
+
+
Gross volume$48,210.40+8.2%
+
Payments1,204+3.1%
+
Refund rate0.8%-0.2 pts
+
+
+ + + +
diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-tabs/component.js b/external/develop/frontend/design-taste-frontend/assets/components/fx-tabs/component.js new file mode 100644 index 0000000..0be0e6a --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-tabs/component.js @@ -0,0 +1,66 @@ +// fx-tabs: the indicator pill is the effect host; it glides to the chosen tab. The effect is a still, +// low-contrast grain in the accent (no motion, no sheen), so the pill reads as a flat accent with a +// printed texture and the label stays clear. Being still, it is drawn once and kept as a picture (no +// WebGL context at rest). Full keyboard support (arrows, Home, End) with roving +// tabindex, as the ARIA tabs pattern asks. +import { still, palette, accentPalette, bag } from '../shared.js'; + +export default function init(root) { + const b = bag(); + const bar = root.querySelector('[role="tablist"]'); + const ind = root.querySelector('.cx-tabs__ind'); + const tabs = [...bar.querySelectorAll('[role="tab"]')]; + const page = palette(root); + const effect = root.dataset.cxEffect || 'grain-gradient'; + // 'flat' mounts nothing: the pill keeps its plain accent fill + if (effect !== 'flat') { + b.add(still(ind, effect, { + palette: accentPalette(page, { lift: 0.05 }), + seed: root.dataset.cxSeed, + params: { speed: 0, grainMotion: 0, grain: 0.32, contrast: 0.18, tint: 0.4, softness: 0.8, warp: 0.3, calm: 0 }, + })); + } + + const place = (tab, animate = true) => { + if (!animate) ind.style.transition = 'none'; + ind.style.setProperty('--x', `${tab.offsetLeft}px`); + ind.style.setProperty('--w', `${tab.offsetWidth}px`); + if (!animate) requestAnimationFrame(() => { ind.style.transition = ''; }); + }; + const choose = (tab, focus = true) => { + tabs.forEach(t => { + const on = t === tab; + t.setAttribute('aria-selected', String(on)); + t.tabIndex = on ? 0 : -1; + const panel = root.querySelector('#' + t.getAttribute('aria-controls')); + if (panel) panel.hidden = !on; + }); + place(tab); + if (focus) tab.focus(); + tab.scrollIntoView({ block: 'nearest', inline: 'nearest' }); + }; + const click = e => { const t = e.target.closest('[role="tab"]'); if (t) choose(t, false); }; + const key = e => { + const i = tabs.indexOf(document.activeElement); + if (i < 0) return; + const n = e.key === 'ArrowRight' ? i + 1 : e.key === 'ArrowLeft' ? i - 1 : e.key === 'Home' ? 0 : e.key === 'End' ? tabs.length - 1 : null; + if (n == null) return; + e.preventDefault(); + choose(tabs[(n + tabs.length) % tabs.length]); + }; + bar.addEventListener('click', click); + bar.addEventListener('keydown', key); + // the faded far edge shows only while there is more bar to scroll to + const scroller = root.querySelector('.cx-tabs__scroll'); + const edge = () => scroller.classList.toggle('is-end', scroller.scrollLeft + scroller.clientWidth >= scroller.scrollWidth - 2); + scroller.addEventListener('scroll', edge, { passive: true }); + const current = () => tabs.find(t => t.getAttribute('aria-selected') === 'true') || tabs[0]; + place(current(), false); + const ro = new ResizeObserver(() => { place(current(), false); edge(); }); + ro.observe(bar); + ro.observe(scroller); + document.fonts?.ready.then(() => { place(current(), false); edge(); }); + edge(); + b.add(() => { ro.disconnect(); bar.removeEventListener('click', click); bar.removeEventListener('keydown', key); scroller.removeEventListener('scroll', edge); }); + return b; +} diff --git a/external/develop/frontend/design-taste-frontend/assets/components/fx-tabs/meta.json b/external/develop/frontend/design-taste-frontend/assets/components/fx-tabs/meta.json new file mode 100644 index 0000000..90c4043 --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/fx-tabs/meta.json @@ -0,0 +1,10 @@ +{ + "id": "fx-tabs", + "name": "Tabs with a grain indicator", + "for": "A tab bar for a product tour or a dashboard preview: the active pill is a small window of a still, low-contrast grain in the accent, and it glides to the tab you choose while the panel below changes.", + "effects": ["grain-gradient", "flat"], + "default": "grain-gradient", + "how": "The effect spans the whole bar and stays put, in a palette within a few steps of the accent so the active label reads; the pill is a clip-path window that slides over it, so nothing is redrawn or resized. The grain is still (speed 0, grainMotion 0). 'flat' mounts nothing and keeps the plain accent. On a phone the bar scrolls sideways with a faded far edge.", + "rules": ["One tab bar like this per page; the label on it keeps 4.5:1.", "The pill never shines, folds or streaks: no silk, glass or metal looks, no motion inside it.", "The indicator moves only when a tab is chosen, never on a timer.", "Panels hold real product content, not lorem."], + "a11y": "ARIA tabs: tablist, tabs with aria-selected and aria-controls, roving tabindex, arrow keys, Home and End; panels are focusable. Reduced motion jumps instead of gliding." +} diff --git a/external/develop/frontend/design-taste-frontend/assets/components/index.html b/external/develop/frontend/design-taste-frontend/assets/components/index.html new file mode 100644 index 0000000..4a8b34a --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/index.html @@ -0,0 +1,365 @@ + + + + + + +Effect components + + + + + + + + +
+
+
Effect components
+ +
+
+ +
+
+
+

Effects as parts of the page, not only behind it

+

Feature cards, real objects drawn in dither, type, numbers, buttons, borders, icons, a street map of the real place, a globe, charts and a photograph in its own colours. Every part takes its colours from the page palette: switch it at the top right. Parts that loop have a pause button.

+
+ +
+
+
+ +
+
+

Each component is a folder in assets/components with its markup, CSS, script and a meta.json that says what it is for and which effects suit it. Use one strong effect moment per page; the rest stay quiet. _lab.html?c=fx-chart&fx=ruled-grounds&palette=white-link-blue shows one part with another effect.

+

Brand marks from Simple Icons. Meeting room photograph by Breather (Unsplash licence), from the skill's photo kit. Map data © OpenStreetMap contributors.

+
+
+ + + + + diff --git a/external/develop/frontend/design-taste-frontend/assets/components/shared.js b/external/develop/frontend/design-taste-frontend/assets/components/shared.js new file mode 100644 index 0000000..8fb5a49 --- /dev/null +++ b/external/develop/frontend/design-taste-frontend/assets/components/shared.js @@ -0,0 +1,569 @@ +/* + taste-skill components - shared helpers. Every component imports this file and fx.js from + ../effects/. A component folder holds component.html (the markup), component.css, component.js + (export default init(root) -> { destroy }) and meta.json. + + Four ways a component puts an effect somewhere other than a background: + fill(el, id) the glyphs of el (or its [data-fill] parts) show the effect: type, numbers, icons, logos + windows(el, ...) one effect seen only through a few shapes (icon chips, bars, cards): one canvas, many holes + ring a host with the class cx-ring shows its effect only in a thin border (padding = width) + glow a host with the class cx-glow is blurred into a soft light behind an object + All of them take their colours from the page palette (fx.readPalette), never a fixed hue. + + Every part whose motion runs on its own for more than about 5 s gets pauser(): a round pause button + in one corner of the looping visual (WCAG 2.2.2). + + Cost (a laptop's integrated GPU shares its memory with everything else): + still(el, id) an effect that does not move is drawn once, kept as a plain picture and its WebGL + context freed: it costs no context and no frames at rest. Stills draw one after + another, so they never hold more than one context at a time on the whole page. + fill(el, id, { still: true }) does the same inside glyphs. + a page keeps at most 3 live WebGL contexts (moving effects only); everything pauses off screen + and in a hidden tab (fx.js does this for every mounted effect). +*/ +import fx from '../effects/fx.js'; + +export { fx }; +export const color = fx.color; + +const mqReduce = window.matchMedia('(prefers-reduced-motion: reduce)'); +const mqHover = window.matchMedia('(hover: hover) and (pointer: fine)'); +export const reduced = () => mqReduce.matches; +export const hoverable = () => mqHover.matches; + +export const palette = el => fx.readPalette(el); +export const isDark = p => color.isDark(p.bg); +/** a step of ink over the ground, the same mix the CSS does with color-mix(in oklab, ink k, bg) */ +export const tone = (p, k) => color.mix(p.bg, p.ink, k); + +/** move c's lightness away from `against` until the contrast reaches min */ +export function safe(c, against, min = 4.5) { + let out = c; + const away = color.isDark(against) ? 0.02 : -0.02; + for (let i = 0; i < 40 && color.contrast(out, against) < min; i++) out = color.lighten(out, away); + return out; +} + +/** + The palette for an effect drawn inside glyphs: the opposite ground, so the fill reads as ink. + On a dark page the letters come out pale with the accent moving in them; on a light page dark. +*/ +export function inkPalette(p) { + return fx.normalizePalette({ + bg: p.ink, + surface: color.mix(p.ink, p.bg, 0.1), + ink: p.bg, + muted: color.mix(p.ink, p.bg, 0.45), + accent: safe(p.accent, p.bg, 3), + accentInk: p.accentInk, + }); +} + +/** + The palette for an effect inside an accent-filled control (button, tab, chip): kept close to the + accent, with the light side checked against the label colour so the label always reads. +*/ +export function accentPalette(p, { min = 4.5, lift = 0.1 } = {}) { + const a = p.accent; + const label = p.accentInk; + const towards = color.isDark(label) ? lift : -lift * 0.6; + const deep = color.adjust(a, { l: -towards * 0.5, c: 1.05 }); + const light = safe(color.adjust(a, { l: towards, c: 1.1 }), label, min); + return fx.normalizePalette({ + bg: safe(a, label, min), + surface: safe(deep, label, min), + ink: label, + muted: color.mix(a, label, 0.25), + accent: light, + accentInk: label, + }); +} + +/** mount an effect with a fixed palette; resolves to the fx controller */ +export function mount(el, id, { palette: pal, params, seed } = {}) { + return fx.mount(el, id, { palette: pal || palette(el), params, seed }); +} + +/* ------------------------------------------------------------------ still */ + +// stills render one after another: at most one WebGL context is open for them on the whole page +let stillChain = Promise.resolve(); +// the stills near the screen that have no picture yet. On a CPU renderer each takes about a second (a new +// context, its shader compiled, one frame), so a screenshot taken a fixed time after a scroll catches +// some blank: says how many are still to come, and a screenshot script waits +// until it is gone (it is removed at 0) +const pendingStills = new Set(); +const markPending = () => { + const d = document.documentElement.dataset; + if (pendingStills.size) d.cxBaking = String(pendingStills.size); else delete d.cxBaking; +}; +const nextFrame = () => new Promise(r => requestAnimationFrame(() => r())); +const STILL_CSS = { + position: 'absolute', inset: '0', width: '100%', height: '100%', display: 'block', zIndex: '-1', + pointerEvents: 'none', borderRadius: 'inherit', objectFit: 'cover', +}; + +/* the next frame the runtime draws into this context, copied into a plain 2D canvas placed first in + host (null if no frame comes within a second and a half, e.g. the tab was hidden) */ +function grab(ctrl, gl, cv, host) { + return new Promise(resolve => { + let done = false, timer = 0; + let nudge = 0; + const finish = shot => { + if (done) return; + done = true; + clearTimeout(timer); + clearTimeout(nudge); + delete gl.drawArrays; + resolve(shot); + }; + const own = gl.drawArrays; + gl.drawArrays = function (...a) { + own.apply(this, a); + // read back in the same task as the draw, before the browser presents and clears the buffer. + // The copy is a plain CPU canvas (willReadFrequently): it owns its pixels at once and holds no GPU + // memory + const c = document.createElement('canvas'); + c.width = cv.width; + c.height = cv.height; + c.className = 'cx-still'; + c.setAttribute('aria-hidden', 'true'); + Object.assign(c.style, STILL_CSS, { imageRendering: cv.style.imageRendering || 'auto' }); + host.insertBefore(c, host.firstChild); + const g = c.getContext('2d', { willReadFrequently: true }); + g.drawImage(cv, 0, 0); + finish(c); + }; + timer = setTimeout(() => finish(null), 1500); + ctrl.resume(); + // an effect that holds itself still (a photograph with no drift) draws nothing on resume: set({}) + // asks it for one frame (every effect's controller redraws on set) + nudge = setTimeout(() => { if (!done) { try { ctrl.set?.({}); } catch (_) { /* no frame: finish(null) */ } } }, 250); + }); +} + +/** + still(el, id, opts): an effect that does not move (speed and drift 0), drawn once and kept as a + picture. When el first comes into view the effect is mounted paused, one frame is copied into a plain + 2D canvas where the effect canvas was (with any layer the effect adds, such as a grain), and the + effect and its WebGL context are removed. When el changes size by more than a few percent the picture + is drawn again (until then it is cropped to fit, never stretched). Without WebGL the effect's own CSS + fallback stays; an effect drawn on a 2D canvas is simply kept paused. opts: palette, params, seed, + fade (a short fade-in of the first picture). Returns { el, ready, info, pause(), resume(), destroy() }; + ready resolves once the first picture is in place. +*/ +export function still(el, id, { palette: pal, params, seed, fade = true } = {}) { + // fade: false where the effect hides something of its own that comes back when it is removed (the + // photograph under duotone-grade), so the picture takes its place at once + let destroyed = false, pic = null, extras = [], kept = null, size = null, visible = false, queued = false, timer = 0, tries = 0; + let ready; + const readyP = new Promise(r => { ready = r; }); + const stale = () => { + if (!size) return true; + const w = el.clientWidth, h = el.clientHeight; + return Math.abs(w - size[0]) > Math.max(2, size[0] * 0.04) || Math.abs(h - size[1]) > Math.max(2, size[1] * 0.04); + }; + const token = {}; + const pend = () => { + if (!destroyed && visible && !document.hidden && !pic && !kept && tries <= 3 && el.clientWidth && el.clientHeight) pendingStills.add(token); + else pendingStills.delete(token); + markPending(); + }; + const want = () => { + pend(); + if (destroyed || queued || kept || !visible || document.hidden || !stale() || tries > 3) return; + queued = true; + stillChain = stillChain.then(render, render).then(() => { queued = false; want(); }); + }; + const keep = ctrl => { kept = ctrl; ready(); pend(); }; + + async function render() { + if (destroyed || !visible || document.hidden) return; + const w = el.clientWidth, h = el.clientHeight; + if (!w || !h) return; + const before = new Set(el.children); + let ctrl; + try { ctrl = await fx.mount(el, id, { palette: pal || palette(el), params, seed }); } + catch (err) { console.warn('still:', err); tries = 9; return; } + ctrl.pause(); + // the runtime makes its canvas and draws one frame as soon as el is in view + let cv = null, seenAt = 0; + for (const t0 = performance.now(); ;) { + if (destroyed) { ctrl.destroy(); return; } + if (ctrl.info?.fallback) return keep(ctrl); // the CSS fallback: no context to free + const c = [...el.querySelectorAll(':scope > canvas')].find(n => !before.has(n)); + if (c) { + if (!cv) { cv = c; seenAt = performance.now(); } + if (cv.style.opacity === '1' || performance.now() - seenAt > 600) break; + } + if (performance.now() - t0 > 5000) { // never drawn (scrolled away): try again later + ctrl.destroy(); + if (visible) tries++; + return; + } + await nextFrame(); + } + // not a context of its own: cv is the canvas fx.mount made above, so this returns the context fx.js already counts in + // window.__tpGl (its liveGL set: enforceBudget before the mount, released by ctrl.destroy() below); one still at a time + const gl = cv.getContext('webgl2') || cv.getContext('webgl'); + if (!gl) return keep(ctrl); // drawn on a 2D canvas: paused, it costs nothing + const shot = await grab(ctrl, gl, cv, el); + if (destroyed) { shot?.remove(); ctrl.destroy(); return; } + // no frame came (hidden tab, scrolled away, a renderer that never calls drawArrays): on screen the + // paused effect stays as it is; off screen it goes and the picture is tried again later + if (!shot) { if (visible && !document.hidden) return keep(ctrl); ctrl.destroy(); return; } + const added = [...el.children].filter(n => !before.has(n) && n !== cv && n !== shot); + const copies = added.map(n => { const k = n.cloneNode(true); k.setAttribute('data-cx-still', ''); return k; }); + ctrl.destroy(); + const old = [pic, ...extras]; + shot.after(...copies); + old.forEach(n => n?.remove()); + if (fade && !pic && !reduced()) shot.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 450, easing: 'ease-out' }); + pic = shot; + extras = copies; + size = [w, h]; + ready(); + } + + // the same margin fx.js makes its contexts at, so the picture is ready as el comes into view + const io = new IntersectionObserver(e => { visible = e[e.length - 1].isIntersecting; want(); }, { rootMargin: '12% 0px' }); + io.observe(el); + const ro = new ResizeObserver(() => { clearTimeout(timer); timer = setTimeout(want, size ? 250 : 0); }); + ro.observe(el); + document.addEventListener('visibilitychange', want); + return { + el, + still: true, + ready: readyP, + pause() {}, + resume() {}, + get info() { return kept ? kept.info : { still: true, webgl: 0, running: false, size: pic ? [pic.width, pic.height] : null }; }, + destroy() { + destroyed = true; + pend(); + io.disconnect(); + ro.disconnect(); + clearTimeout(timer); + document.removeEventListener('visibilitychange', want); + kept?.destroy(); + kept = null; + [pic, ...extras].forEach(n => n?.remove()); + pic = null; + extras = []; + }, + }; +} + +/* ------------------------------------------------------------------ fill */ + +/** + fill(el, id, opts): the glyphs of el show the effect. Mark the parts with data-fill (text, an + icon drawn in currentColor, a .cx-glyph mask icon) or data-fill="box" (the element's whole box, + its border-radius kept); with no mark, all of el's text is filled. opts.still: the effect does not + move, so it is drawn once and kept as a picture (see still()). + + How: one effect canvas sits in an overlay placed NEXT TO el (its next sibling, positioned over + el's box), never inside it, so el's own text and HTML stay clean: a