diff --git a/docs/biome.json b/docs/biome.json index dd49529..87cba3b 100644 --- a/docs/biome.json +++ b/docs/biome.json @@ -7,7 +7,15 @@ }, "files": { "ignoreUnknown": true, - "includes": ["**", "!node_modules", "!.next", "!dist", "!build", "!.source"] + "includes": [ + "**", + "!node_modules", + "!.next", + "!dist", + "!build", + "!.source", + "!src/app/global.css" + ] }, "formatter": { "enabled": true, diff --git a/docs/content/docs/api-reference/index.mdx b/docs/content/docs/api-reference/index.mdx new file mode 100644 index 0000000..b778af5 --- /dev/null +++ b/docs/content/docs/api-reference/index.mdx @@ -0,0 +1,77 @@ +--- +title: API Reference +description: Every public class, method, and CLI command in taskito. +--- + +import { Cards, Card } from "fumadocs-ui/components/card"; +import { + Compass, + Inbox, + ListTodo, + CheckCircle2, + Variable, + GitFork, + Workflow, + TestTube, + Terminal, +} from "lucide-react"; + +Reference docs for everything taskito exposes. Each page lists the full +signature, parameters, return values, and a short example. + + + } + title="Overview" + href="/docs/api-reference/overview" + description="The shape of the public API — what's stable, what's experimental." + /> + } + title="Queue" + href="/docs/api-reference/queue" + description="Construct queues, enqueue jobs, manage workers, inspect state." + /> + } + title="Task" + href="/docs/api-reference/task" + description="The @queue.task decorator — options, methods, async variants." + /> + } + title="Result" + href="/docs/api-reference/result" + description="JobResult, polling, awaiting, timeouts, sync and async APIs." + /> + } + title="Context" + href="/docs/api-reference/context" + description="current_job — progress, logging, cancellation, timeouts inside a task." + /> + } + title="Canvas" + href="/docs/api-reference/canvas" + description="chain, group, chord — the Celery-compatible composition primitives." + /> + } + title="Workflows" + href="/docs/api-reference/workflows" + description="The DAG builder — fan-out, fan-in, gates, conditions, sub-workflows." + /> + } + title="Testing" + href="/docs/api-reference/testing" + description="test_mode, MockResource, fixtures for synchronous in-process verification." + /> + } + title="CLI" + href="/docs/api-reference/cli" + description="taskito worker, dashboard, info — every flag and subcommand." + /> + diff --git a/docs/content/docs/architecture/index.mdx b/docs/content/docs/architecture/index.mdx new file mode 100644 index 0000000..b3560bd --- /dev/null +++ b/docs/content/docs/architecture/index.mdx @@ -0,0 +1,70 @@ +--- +title: Architecture +description: How taskito works under the hood — Rust core, scheduler, storage, worker pool. +--- + +import { Cards, Card } from "fumadocs-ui/components/card"; +import { + Compass, + GitBranch, + Layers, + CalendarClock, + Database, + Boxes, + ShieldAlert, + FileCode2, +} from "lucide-react"; + +The internals, explained from the outside in. Start with **Overview** for the +high-level model, then drill into the layer that interests you. + + + } + title="Overview" + href="/docs/architecture/overview" + description="The big picture — Python ↔ PyO3 ↔ Rust core, end to end." + /> + } + title="Job lifecycle" + href="/docs/architecture/job-lifecycle" + description="Every state a job moves through, from enqueue to result or dead letter." + /> + } + title="Worker pool" + href="/docs/architecture/worker-pool" + description="OS-thread pool, prefork children, async executor — how each model dispatches work." + /> + } + title="Scheduler" + href="/docs/architecture/scheduler" + description="The Tokio polling loop — how jobs are picked, claimed, and routed to workers." + /> + } + title="Storage" + href="/docs/architecture/storage" + description="The Storage trait, Diesel for SQLite/Postgres, Redis backend, table schemas." + /> + } + title="Resources" + href="/docs/architecture/resources" + description="The 3-layer DI pipeline — argument interception, worker injection, proxy reconstruction." + /> + } + title="Failure model" + href="/docs/architecture/failure-model" + description="Retries, dead letters, circuit breakers, cancellation — what happens when things go wrong." + /> + } + title="Serialization" + href="/docs/architecture/serialization" + description="Cloudpickle, JSON, MsgPack — how arguments and results cross the worker boundary." + /> + diff --git a/docs/content/docs/architecture/scheduler.mdx b/docs/content/docs/architecture/scheduler.mdx index 65ca5d9..4978a8d 100644 --- a/docs/content/docs/architecture/scheduler.mdx +++ b/docs/content/docs/architecture/scheduler.mdx @@ -5,7 +5,7 @@ description: "The Tokio-based poll loop that dequeues, dispatches, retries, and The scheduler runs in a dedicated Tokio single-threaded async runtime: -``` +```text loop { sleep(50ms) or shutdown signal @@ -23,6 +23,28 @@ loop { } ``` +> **Why those numbers?** 50 ms is fast enough that dequeue latency is imperceptible +> but slow enough to leave the CPU 99% idle when the queue is empty. The periodic +> intervals (~60, ~100, ~1200) are deliberately coprime so the four maintenance +> tasks rarely fire on the same tick — keeping each individual iteration cheap. + +## Polling cycle + + Sleep["sleep 50ms"] + Sleep --> Dispatch["try_dispatch()"] + Dispatch --> Periodic{"iteration
multiple?"} + Periodic -->|"every ~60"| CP["check_periodic()"] + Periodic -->|"every ~100"| RS["reap_stale()"] + Periodic -->|"every ~1200"| AC["auto_cleanup()"] + Periodic -->|"otherwise"| Next["next tick"] + CP --> Next + RS --> Next + AC --> Next + Next --> Tick`} +/> + ## Dispatch flow 1. `dequeue_from()` — atomically `SELECT` + `UPDATE` (pending → running) within a transaction. @@ -30,3 +52,27 @@ loop { 3. Send job to worker pool via `tokio::sync::mpsc` channel. 4. Worker executes task, sends result back. 5. `handle_result()` — mark complete, schedule retry, or move to DLQ. + +>DB: dequeue_from() + DB-->>S: pending job + S->>RL: check(task) + alt over limit + RL-->>S: limited + S->>DB: reschedule +1s + else under limit + RL-->>S: ok + S->>WP: mpsc.send(job) + WP->>W: execute() + W-->>S: result + S->>DB: handle_result() + end`} +/> diff --git a/docs/content/docs/architecture/storage.mdx b/docs/content/docs/architecture/storage.mdx index 5ecc571..9219eff 100644 --- a/docs/content/docs/architecture/storage.mdx +++ b/docs/content/docs/architecture/storage.mdx @@ -14,86 +14,122 @@ description: "SQLite pragmas, schema, indexes, connection pooling, and Postgres ## Database schema - +Six tables in the SQLite backend. Use the arrows or click a dot to flip between tables; the last slide shows their relationships. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ## Key indexes diff --git a/docs/content/docs/guides/index.mdx b/docs/content/docs/guides/index.mdx index 9c0ba0b..262b94d 100644 --- a/docs/content/docs/guides/index.mdx +++ b/docs/content/docs/guides/index.mdx @@ -3,16 +3,75 @@ title: Guides description: Topical guides covering taskito's surface area. --- -import { Cards, Card } from 'fumadocs-ui/components/card'; +import { Cards, Card } from "fumadocs-ui/components/card"; +import { + Boxes, + ShieldCheck, + Cpu, + Wrench, + Activity, + Layers, + Workflow, + Plug, + Puzzle, +} from "lucide-react"; + +Pick the topic you're working through. Each guide is self-contained, with code +samples, gotchas, and links to the relevant API reference. - - - - - - - - - + } + title="Core" + href="/docs/guides/core" + description="Define tasks, route to queues, run workers, schedule periodics." + /> + } + title="Reliability" + href="/docs/guides/reliability" + description="Retries, dead letters, circuit breakers, idempotency, error handling." + /> + } + title="Advanced execution" + href="/docs/guides/advanced-execution" + description="Prefork pools, native async, streaming, cancellation, soft timeouts." + /> + } + title="Operations" + href="/docs/guides/operations" + description="Deployment, scaling, namespaces, migration from Celery." + /> + } + title="Observability" + href="/docs/guides/observability" + description="Metrics, logs, dashboard, OpenTelemetry, Sentry, Prometheus." + /> + } + title="Resources" + href="/docs/guides/resources" + description="Inject DB clients, HTTP sessions, and cloud SDKs by name with hot reload." + /> + } + title="Workflows" + href="/docs/guides/workflows" + description="DAGs, fan-out, fan-in, conditions, gates, sub-workflows, incremental re-runs." + /> + } + title="Integrations" + href="/docs/guides/integrations" + description="Django, FastAPI, Flask — admin views, REST routers, management commands." + /> + } + title="Extensibility" + href="/docs/guides/extensibility" + description="Custom serializers, middleware, events, webhooks, proxies." + /> diff --git a/docs/content/docs/index.mdx b/docs/content/docs/index.mdx index c73649e..7e42a9a 100644 --- a/docs/content/docs/index.mdx +++ b/docs/content/docs/index.mdx @@ -4,6 +4,7 @@ description: Rust-powered task queue for Python. No broker required. --- import { Cards, Card } from "fumadocs-ui/components/card"; +import { Rocket, BookOpen, Boxes, Code2 } from "lucide-react"; A brokerless, Rust-powered task queue for Python. Replace Celery without Redis. @@ -26,23 +27,27 @@ Python only runs during actual task execution. } title="Getting Started" href="/docs/getting-started/installation" description="Install and run your first task in 5 minutes." /> } title="Guides" href="/docs/guides" - description="Core, reliability, advanced execution, operations, observability." + description="Topical walkthroughs grouped by intent — core, reliability, advanced execution, operations, more." /> } title="Architecture" - href="/docs/architecture/overview" - description="How taskito is built — Rust core, PyO3 boundary, storage layer." + href="/docs/architecture" + description="How the Rust core, scheduler, storage, and worker pool fit together." /> } title="API Reference" - href="/docs/api-reference/overview" - description="Complete API surface for the Queue, tasks, results, locks, and workflows." + href="/docs/api-reference" + description="Every public class, method, and CLI command with signatures and examples." /> diff --git a/docs/src/app/(home)/_sections/how-it-works.tsx b/docs/src/app/(home)/_sections/how-it-works.tsx new file mode 100644 index 0000000..b0672fc --- /dev/null +++ b/docs/src/app/(home)/_sections/how-it-works.tsx @@ -0,0 +1,317 @@ +import type { ReactNode } from "react"; + +type Station = { + id: string; + label: string; + hint: string; + cx: number; + glyph: ReactNode; +}; + +const VIEW_W = 460; +const VIEW_H = 130; +const TRACK_Y = 50; +const STATION_W = 56; +const STATION_H = 44; + +const STATIONS: Station[] = [ + { + id: "enqueue", + label: "enqueue", + hint: ".delay()", + cx: 56, + glyph: , + }, + { + id: "queue", + label: "queue", + hint: "SQLite · Postgres", + cx: 184, + glyph: , + }, + { + id: "worker", + label: "worker", + hint: "Rust pool", + cx: 304, + glyph: , + }, + { + id: "result", + label: "result", + hint: ".result()", + cx: 412, + glyph: , + }, +]; + +const JOB_DELAYS = ["0s", "-1.7s", "-3.4s"] as const; + +export function HowItWorks() { + return ( +
+
+

+ how it works +

+

+ Your code calls{" "} + .delay() · the + job durably queues · a Rust scheduler routes it · a worker returns the + result. +

+
+
+ + + + + + + + + + + + {STATIONS.slice(0, -1).map((station, i) => { + const next = STATIONS[i + 1]; + return ( + + ); + })} + + {STATIONS.map((station) => ( + + ))} + + {JOB_DELAYS.map((delay, index) => ( + + ))} + +
+ +
+ ); +} + +function StationNode({ station }: { station: Station }) { + const isWorker = station.id === "worker"; + const x = station.cx - STATION_W / 2; + const y = TRACK_Y - STATION_H / 2; + return ( + + + + {station.glyph} + + {isWorker ? ( + + ) : null} + + {station.label} + + + {station.hint} + + + ); +} + +function CodeGlyph() { + return ( + + code + + + + ); +} + +function DatabaseGlyph() { + return ( + + database + + + + + ); +} + +function WorkerGlyph() { + return ( + + worker pool + + + + + ); +} + +function CheckGlyph() { + return ( + + check + + + ); +} + +function FlowStyles() { + return ( + + ); +} diff --git a/docs/src/app/(home)/_sections/index.ts b/docs/src/app/(home)/_sections/index.ts index 272f7e4..5a5c059 100644 --- a/docs/src/app/(home)/_sections/index.ts +++ b/docs/src/app/(home)/_sections/index.ts @@ -2,3 +2,7 @@ export { ComparisonSection } from "./comparison-section"; export { CTA } from "./cta"; export { Features } from "./features"; export { Hero } from "./hero"; +export { HowItWorks } from "./how-it-works"; +export { Integrations } from "./integrations"; +export { UseCases } from "./use-cases"; +export { WorkerPool } from "./worker-pool"; diff --git a/docs/src/app/(home)/_sections/integrations.tsx b/docs/src/app/(home)/_sections/integrations.tsx new file mode 100644 index 0000000..ca0eb69 --- /dev/null +++ b/docs/src/app/(home)/_sections/integrations.tsx @@ -0,0 +1,39 @@ +import { SectionHeader } from "@/components/ui"; +import { + INTEGRATIONS, + INTEGRATIONS_DESCRIPTION, + INTEGRATIONS_TITLE, +} from "@/lib/landing-content"; + +export function Integrations() { + return ( +
+ +
+ {INTEGRATIONS.map((group) => ( +
+
+ {group.group} +
+
+ {group.items.map((item) => ( + + {item} + + ))} +
+
+ ))} +
+
+ ); +} diff --git a/docs/src/app/(home)/_sections/use-cases.tsx b/docs/src/app/(home)/_sections/use-cases.tsx new file mode 100644 index 0000000..3458d3d --- /dev/null +++ b/docs/src/app/(home)/_sections/use-cases.tsx @@ -0,0 +1,52 @@ +import { ArrowUpRight } from "lucide-react"; +import Link from "next/link"; +import { SectionHeader } from "@/components/ui"; +import { + USE_CASES, + USE_CASES_DESCRIPTION, + USE_CASES_TITLE, + type UseCase, +} from "@/lib/landing-content"; + +export function UseCases() { + return ( +
+ +
+ {USE_CASES.map((useCase) => ( + + ))} +
+
+ ); +} + +function UseCaseCard({ useCase }: { useCase: UseCase }) { + const { icon: Icon, title, body, href } = useCase; + return ( + +
+
+
+
+ +
+ +
+

{title}

+

+ {body} +

+
+ + ); +} diff --git a/docs/src/app/(home)/_sections/worker-pool.tsx b/docs/src/app/(home)/_sections/worker-pool.tsx new file mode 100644 index 0000000..bb8a173 --- /dev/null +++ b/docs/src/app/(home)/_sections/worker-pool.tsx @@ -0,0 +1,312 @@ +const WORKERS = [ + { id: 0, x: 0, y: 0, delay: "0s", duration: "2.4s" }, + { id: 1, x: 1, y: 0, delay: "-0.4s", duration: "2.8s" }, + { id: 2, x: 2, y: 0, delay: "-1.1s", duration: "2.6s" }, + { id: 3, x: 0, y: 1, delay: "-0.7s", duration: "3.1s" }, + { id: 4, x: 1, y: 1, delay: "-1.6s", duration: "2.5s" }, + { id: 5, x: 2, y: 1, delay: "-0.2s", duration: "2.9s" }, +] as const; + +const INCOMING_JOBS = [ + { delay: "0s" }, + { delay: "-0.6s" }, + { delay: "-1.2s" }, + { delay: "-1.8s" }, +] as const; + +const RESULTS = [ + { delay: "-0.3s" }, + { delay: "-0.9s" }, + { delay: "-1.5s" }, +] as const; + +const VIEW_W = 580; +const VIEW_H = 170; +const POOL_LEFT = 210; +const POOL_TOP = 50; +const WORKER_SIZE = 38; +const WORKER_GAP = 12; +const POOL_WIDTH = 3 * WORKER_SIZE + 2 * WORKER_GAP; +const POOL_HEIGHT = 2 * WORKER_SIZE + 1 * WORKER_GAP; +const POOL_RIGHT = POOL_LEFT + POOL_WIDTH; +const TRACK_Y = POOL_TOP + POOL_HEIGHT / 2; +const INCOMING_LEFT = 30; +const RESULT_LEFT = POOL_RIGHT + 30; +const RESULT_END = VIEW_W - 30; + +export function WorkerPool() { + return ( +
+
+

+ six workers, constant flow +

+

+ Jobs stream in, workers pick them up, results stream out. No broker + between them. +

+
+
+ + + + + + + + + + + + + + + {INCOMING_JOBS.map((job, i) => ( + + ))} + + {WORKERS.map((worker) => ( + + ))} + + {RESULTS.map((result, i) => ( + + + + + ))} + + + 6 workers · 0 brokers + + +
+ +
+ ); +} + +function LaneLabels() { + const labels = [ + { x: INCOMING_LEFT, text: "incoming" }, + { x: POOL_LEFT, text: "workers" }, + { x: RESULT_LEFT, text: "results" }, + ]; + return ( + <> + {labels.map((label) => ( + + {label.text} + + ))} + + ); +} + +function Lanes() { + return ( + <> + + + + ); +} + +function Worker({ worker }: { worker: (typeof WORKERS)[number] }) { + const x = POOL_LEFT + worker.x * (WORKER_SIZE + WORKER_GAP); + const y = POOL_TOP + worker.y * (WORKER_SIZE + WORKER_GAP); + const cx = x + WORKER_SIZE / 2; + const cy = y + WORKER_SIZE / 2; + return ( + + + + + + + ); +} + +function PoolStyles() { + return ( + + ); +} diff --git a/docs/src/app/(home)/page.tsx b/docs/src/app/(home)/page.tsx index 8ad91ad..dc0dc09 100644 --- a/docs/src/app/(home)/page.tsx +++ b/docs/src/app/(home)/page.tsx @@ -1,10 +1,23 @@ -import { ComparisonSection, CTA, Features, Hero } from "./_sections"; +import { + ComparisonSection, + CTA, + Features, + Hero, + HowItWorks, + Integrations, + UseCases, + WorkerPool, +} from "./_sections"; export default function HomePage() { return (
+ + + +
diff --git a/docs/src/app/global.css b/docs/src/app/global.css index 488ce0e..b9e26c0 100644 --- a/docs/src/app/global.css +++ b/docs/src/app/global.css @@ -2,6 +2,21 @@ @import "fumadocs-ui/css/neutral.css"; @import "fumadocs-ui/css/preset.css"; +@theme { + --font-sans: + var(--font-sans), "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif; + --font-mono: + var(--font-mono), "IBM Plex Mono", ui-monospace, SFMono-Regular, + "Cascadia Mono", monospace; + --font-handwritten: var(--font-handwritten), "Shantell Sans", "Caveat", cursive; + --color-fd-sketch: + color-mix(in oklch, var(--color-fd-foreground) 75%, transparent); + --color-fd-sketch-strong: + color-mix(in oklch, var(--color-fd-foreground) 92%, transparent); + --color-fd-sketch-accent: + color-mix(in oklch, var(--color-fd-primary) 80%, transparent); +} + html { scrollbar-gutter: stable; } diff --git a/docs/src/app/layout.tsx b/docs/src/app/layout.tsx index c77d7cc..d42bee3 100644 --- a/docs/src/app/layout.tsx +++ b/docs/src/app/layout.tsx @@ -1,10 +1,23 @@ import type { Metadata } from "next"; -import { Inter } from "next/font/google"; +import { IBM_Plex_Mono, IBM_Plex_Sans, Shantell_Sans } from "next/font/google"; import { Provider } from "@/components/provider"; import "./global.css"; -const inter = Inter({ +const ibmPlexSans = IBM_Plex_Sans({ subsets: ["latin"], + weight: ["400", "500", "600", "700"], + variable: "--font-sans", +}); + +const ibmPlexMono = IBM_Plex_Mono({ + subsets: ["latin"], + weight: ["400", "500", "600"], + variable: "--font-mono", +}); + +const shantellSans = Shantell_Sans({ + subsets: ["latin"], + variable: "--font-handwritten", }); export const metadata: Metadata = { @@ -18,7 +31,11 @@ export const metadata: Metadata = { export default function Layout({ children }: LayoutProps<"/">) { return ( - + {children} diff --git a/docs/src/components/diagram-carousel.tsx b/docs/src/components/diagram-carousel.tsx new file mode 100644 index 0000000..dbe690f --- /dev/null +++ b/docs/src/components/diagram-carousel.tsx @@ -0,0 +1,149 @@ +"use client"; + +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { + Children, + isValidElement, + type ReactElement, + type ReactNode, + useCallback, + useEffect, + useRef, + useState, +} from "react"; +import { cn } from "@/lib/cn"; + +type DiagramSlideProps = { + title?: string; + children: ReactNode; +}; + +export function DiagramSlide({ children }: DiagramSlideProps) { + return <>{children}; +} + +type SlideElement = ReactElement; + +export function DiagramCarousel({ + children, + title, +}: { + children: ReactNode; + title?: string; +}) { + const slides = Children.toArray(children).filter( + (child): child is SlideElement => isValidElement(child), + ); + const total = slides.length; + const containerRef = useRef(null); + const [index, setIndex] = useState(0); + + const prev = useCallback( + () => setIndex((i) => (i - 1 + total) % total), + [total], + ); + const next = useCallback(() => setIndex((i) => (i + 1) % total), [total]); + + useEffect(() => { + const node = containerRef.current; + if (!node) return; + function onKey(e: KeyboardEvent) { + if (e.key === "ArrowLeft") { + e.preventDefault(); + prev(); + } else if (e.key === "ArrowRight") { + e.preventDefault(); + next(); + } + } + node.addEventListener("keydown", onKey); + return () => node.removeEventListener("keydown", onKey); + }, [prev, next]); + + if (total === 0) return null; + + const activeTitle = slides[index]?.props.title; + + return ( +
} + className="taskito-carousel my-6 rounded-lg border border-fd-border bg-fd-card overflow-hidden focus:outline-none focus:ring-2 focus:ring-fd-primary/40" + // biome-ignore lint/a11y/noNoninteractiveTabindex: container hosts arrow-key navigation per WAI-ARIA carousel pattern + tabIndex={0} + aria-roledescription="carousel" + aria-label={title ?? "Diagram carousel"} + > +
+
+ {title && ( + + {title} + + )} + {activeTitle && ( + + {title ? "·" : ""} {activeTitle} + + )} +
+
+ + {index + 1} / {total} + +
+ + +
+
+
+
+ {slides.map((slide, i) => ( +
+ {slide} +
+ ))} +
+
+ {slides.map((slide, i) => { + const slideTitle = slide.props.title; + return ( +
+
+ ); +} diff --git a/docs/src/components/mdx.tsx b/docs/src/components/mdx.tsx index 1e29908..00c1d1d 100644 --- a/docs/src/components/mdx.tsx +++ b/docs/src/components/mdx.tsx @@ -1,11 +1,14 @@ import defaultMdxComponents from "fumadocs-ui/mdx"; import type { MDXComponents } from "mdx/types"; +import { DiagramCarousel, DiagramSlide } from "./diagram-carousel"; import { Mermaid } from "./mermaid"; export function getMDXComponents(components?: MDXComponents) { return { ...defaultMdxComponents, Mermaid, + DiagramCarousel, + DiagramSlide, ...components, } satisfies MDXComponents; } diff --git a/docs/src/components/mermaid.tsx b/docs/src/components/mermaid.tsx index ecbcfa2..b4a78bd 100644 --- a/docs/src/components/mermaid.tsx +++ b/docs/src/components/mermaid.tsx @@ -3,6 +3,90 @@ import { useTheme } from "next-themes"; import { useEffect, useId, useRef, useState } from "react"; +const DARK_THEME_VARIABLES = { + background: "transparent", + primaryColor: "#1f2024", + primaryTextColor: "#f5f5f7", + primaryBorderColor: "#7a7d85", + lineColor: "#9b9ea6", + secondaryColor: "#2a2c31", + tertiaryColor: "#26282d", + mainBkg: "#1f2024", + nodeBkg: "#1f2024", + nodeBorder: "#7a7d85", + clusterBkg: "#1a1b1e", + clusterBorder: "#5a5d65", + edgeLabelBackground: "#2a2c31", + titleColor: "#f5f5f7", + labelTextColor: "#f5f5f7", + textColor: "#f5f5f7", + noteBkgColor: "#3a3c41", + noteTextColor: "#f5f5f7", + noteBorderColor: "#7a7d85", + errorBkgColor: "#4a1d1d", + errorTextColor: "#fca5a5", + // erDiagram-specific (entity attribute rows) + attributeBackgroundColorOdd: "#1f2024", + attributeBackgroundColorEven: "#272a30", + // stateDiagram-specific + altBackground: "#26282d", + // flowchart label colors + labelBackground: "#2a2c31", + // sequenceDiagram + actorBkg: "#1f2024", + actorBorder: "#7a7d85", + actorTextColor: "#f5f5f7", + actorLineColor: "#9b9ea6", + signalColor: "#f5f5f7", + signalTextColor: "#f5f5f7", + labelBoxBkgColor: "#2a2c31", + labelBoxBorderColor: "#7a7d85", + loopTextColor: "#f5f5f7", + activationBkgColor: "#3a3c41", + activationBorderColor: "#9b9ea6", +} as const; + +const LIGHT_THEME_VARIABLES = { + background: "transparent", + primaryColor: "#ffffff", + primaryTextColor: "#1a1a1a", + primaryBorderColor: "#3a3a3a", + lineColor: "#4a4a4a", + secondaryColor: "#f4f4f5", + tertiaryColor: "#fafafa", + mainBkg: "#ffffff", + nodeBkg: "#ffffff", + nodeBorder: "#3a3a3a", + clusterBkg: "#f4f4f5", + clusterBorder: "#a1a1aa", + edgeLabelBackground: "#ffffff", + titleColor: "#1a1a1a", + labelTextColor: "#1a1a1a", + textColor: "#1a1a1a", + noteBkgColor: "#fef9c3", + noteTextColor: "#1a1a1a", + noteBorderColor: "#a1a1aa", + // erDiagram-specific (entity attribute rows) + attributeBackgroundColorOdd: "#ffffff", + attributeBackgroundColorEven: "#f4f4f5", + // stateDiagram-specific + altBackground: "#f4f4f5", + // flowchart label colors + labelBackground: "#ffffff", + // sequenceDiagram + actorBkg: "#ffffff", + actorBorder: "#3a3a3a", + actorTextColor: "#1a1a1a", + actorLineColor: "#4a4a4a", + signalColor: "#1a1a1a", + signalTextColor: "#1a1a1a", + labelBoxBkgColor: "#ffffff", + labelBoxBorderColor: "#3a3a3a", + loopTextColor: "#1a1a1a", + activationBkgColor: "#f4f4f5", + activationBorderColor: "#4a4a4a", +} as const; + export function Mermaid({ chart }: { chart: string }) { const id = useId(); const containerRef = useRef(null); @@ -13,11 +97,22 @@ export function Mermaid({ chart }: { chart: string }) { let cancelled = false; void (async () => { const mermaid = (await import("mermaid")).default; + if (typeof document !== "undefined" && document.fonts?.ready) { + await document.fonts.ready; + } + const isDark = resolvedTheme === "dark"; mermaid.initialize({ startOnLoad: false, - theme: resolvedTheme === "dark" ? "dark" : "default", + theme: "base", + themeVariables: isDark ? DARK_THEME_VARIABLES : LIGHT_THEME_VARIABLES, securityLevel: "loose", - fontFamily: "inherit", + fontFamily: '"IBM Plex Sans", "Inter", system-ui, sans-serif', + flowchart: { padding: 18, htmlLabels: true, useMaxWidth: true }, + sequence: { + actorFontFamily: '"IBM Plex Sans", sans-serif', + noteFontFamily: '"IBM Plex Sans", sans-serif', + messageFontFamily: '"IBM Plex Sans", sans-serif', + }, }); try { const renderId = `m${id.replace(/[^a-zA-Z0-9]/g, "")}`; @@ -39,7 +134,7 @@ export function Mermaid({ chart }: { chart: string }) { return (
diff --git a/docs/src/lib/landing-content.tsx b/docs/src/lib/landing-content.tsx index 295aeb6..ad092be 100644 --- a/docs/src/lib/landing-content.tsx +++ b/docs/src/lib/landing-content.tsx @@ -1,8 +1,11 @@ import { Activity, + Clock, Cpu, + Database, Layers, type LucideIcon, + Mail, Shield, Workflow, Zap, @@ -199,6 +202,72 @@ send_email.delay("alice@example.com", "Hi", "Body") ], }; +export type UseCase = { + icon: LucideIcon; + title: string; + body: string; + href: string; +}; + +export const USE_CASES_TITLE = "Built for the jobs you actually have"; +export const USE_CASES_DESCRIPTION = + "Pick the workload — taskito ships the primitives."; + +export const USE_CASES: UseCase[] = [ + { + icon: Database, + title: "ETL pipelines", + body: "Chain extract → transform → load as a DAG. Fan out across workers, fan in to aggregate, restart from any node on failure.", + href: "/docs/guides/workflows", + }, + { + icon: Mail, + title: "Email & notifications", + body: "Bursty SMTP, push, or webhook delivery. Per-task rate limits keep providers happy; retries with backoff handle transient failures.", + href: "/docs/guides/reliability/retries", + }, + { + icon: Cpu, + title: "ML inference & batch", + body: "Long-running model jobs with progress tracking, soft timeouts, and prefork pools for true CPU parallelism without GIL contention.", + href: "/docs/guides/advanced-execution/prefork", + }, + { + icon: Clock, + title: "Scheduled jobs", + body: "Six-field cron syntax down to the second. Periodic tasks live in the scheduler — no separate beat daemon to babysit.", + href: "/docs/guides/core/scheduling", + }, +]; + +export const INTEGRATIONS_TITLE = "Slots into your stack"; +export const INTEGRATIONS_DESCRIPTION = + "First-class support for the tools you already run."; + +export type IntegrationGroup = { + group: string; + items: string[]; +}; + +export const INTEGRATIONS: IntegrationGroup[] = [ + { + group: "Frameworks", + items: ["Django", "FastAPI", "Flask"], + }, + { + group: "Storage", + items: ["Postgres", "SQLite", "Redis"], + }, + { + group: "Observability", + items: ["OpenTelemetry", "Sentry", "Prometheus"], + }, + { + group: "Serialization", + items: ["JSON", "MsgPack", "Cloudpickle"], + }, +]; + export const CTA: LandingCta = { title: "Five minutes from `pip install` to your first job.", description: