From 94c54f30f7032e7d0f01cde9dc3dfcfa7e4bb5c8 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 12 Jul 2026 17:35:19 +0530 Subject: [PATCH 01/12] docs: link shared keda + prometheus in Java nav --- docs/content/docs/java/guides/integrations/meta.json | 2 +- docs/content/docs/java/guides/operations/meta.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/docs/java/guides/integrations/meta.json b/docs/content/docs/java/guides/integrations/meta.json index 995a6027..883db2eb 100644 --- a/docs/content/docs/java/guides/integrations/meta.json +++ b/docs/content/docs/java/guides/integrations/meta.json @@ -1,4 +1,4 @@ { "title": "Integrations", - "pages": ["index", "spring", "micrometer", "sentry"] + "pages": ["index", "spring", "micrometer", "prometheus", "sentry"] } diff --git a/docs/content/docs/java/guides/operations/meta.json b/docs/content/docs/java/guides/operations/meta.json index 40bb2fc1..66d2416b 100644 --- a/docs/content/docs/java/guides/operations/meta.json +++ b/docs/content/docs/java/guides/operations/meta.json @@ -1 +1 @@ -{ "title": "Operations", "pages": ["backends", "inspection", "dashboard", "sso", "mesh", "autoscaling", "cli", "testing", "security", "troubleshooting", "deployment", "graalvm"] } +{ "title": "Operations", "pages": ["backends", "inspection", "dashboard", "sso", "mesh", "autoscaling", "keda", "cli", "testing", "security", "troubleshooting", "deployment", "graalvm"] } From 2e0a43f571817f49e6e189e584c1018faa0c5099 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 12 Jul 2026 17:35:26 +0530 Subject: [PATCH 02/12] docs: make architecture cards + mesh doc SDK-neutral --- docs/content/docs/architecture/index.mdx | 4 ++-- docs/content/docs/architecture/mesh.mdx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/content/docs/architecture/index.mdx b/docs/content/docs/architecture/index.mdx index f0cd98be..8447cf63 100644 --- a/docs/content/docs/architecture/index.mdx +++ b/docs/content/docs/architecture/index.mdx @@ -53,7 +53,7 @@ high-level model, then drill into the layer that interests you. icon={} title="Resources" href="/architecture/resources" - description="The 3-layer DI pipeline — argument interception, worker injection, proxy reconstruction." + description="The dependency-injection pipeline — worker injection, scopes, and per-SDK argument handling." /> } @@ -65,6 +65,6 @@ high-level model, then drill into the layer that interests you. icon={} title="Serialization" href="/architecture/serialization" - description="Cloudpickle, JSON, MsgPack — how arguments and results cross the worker boundary." + description="JSON, MsgPack, CBOR, and language-native codecs — how arguments and results cross the worker boundary." /> diff --git a/docs/content/docs/architecture/mesh.mdx b/docs/content/docs/architecture/mesh.mdx index 16e594fb..11bd74e3 100644 --- a/docs/content/docs/architecture/mesh.mdx +++ b/docs/content/docs/architecture/mesh.mdx @@ -249,8 +249,8 @@ after a period of queue emptiness. The mesh does **not** modify the [`Scheduler`](/architecture/scheduler) struct or the [`WorkerDispatcher`](/architecture/worker-pool) trait. -Instead, `run_worker` (in `crates/taskito-python/src/py_queue/worker.rs`) -spawns a **mesh bridge** — an intermediate `tokio::sync::mpsc` channel +Instead, the per-SDK worker entrypoint spawns a **mesh bridge** — an +intermediate `tokio::sync::mpsc` channel between the scheduler and dispatcher: The `run_mesh_bridge()` function: From bd1cfcc5d388490d65010b25ac7f3cf0527f48ef Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 12 Jul 2026 17:35:31 +0530 Subject: [PATCH 03/12] docs: SDK-gate the queue comparison page --- docs/content/docs/resources/comparison.mdx | 94 +++++++++++++++++----- 1 file changed, 73 insertions(+), 21 deletions(-) diff --git a/docs/content/docs/resources/comparison.mdx b/docs/content/docs/resources/comparison.mdx index d974de5d..6e337fd0 100644 --- a/docs/content/docs/resources/comparison.mdx +++ b/docs/content/docs/resources/comparison.mdx @@ -1,18 +1,39 @@ --- title: Comparison -description: "Taskito vs Celery, RQ, Dramatiq, Huey, TaskIQ — feature matrix and decision guide." +description: "Taskito vs other task queues — feature matrix and decision guide." --- **TL;DR**: Taskito is without the broker (a separate server such as Redis or RabbitMQ that holds pending jobs). Rust scheduler, no Redis/RabbitMQ, lower latency, better concurrency. Start with SQLite, scale to Postgres when needed. +## When to use taskito + +taskito is ideal when: + +- **Single-machine deployments** — no need for distributed workers across multiple servers +- **Zero infrastructure** — you don't want to install, configure, or manage Redis or RabbitMQ +- **Embedded applications** — CLI tools, desktop apps, or services where simplicity matters +- **Prototyping** — get a task queue running in 5 lines, iterate fast +- **Low-to-medium throughput** — hundreds to thousands of jobs per second is plenty + +## When NOT to use taskito + +Consider alternatives when: + +- **Multi-server workers** — you need workers on separate machines (taskito supports this with Postgres/Redis backends, but a broker-based queue has more mature distributed tooling) +- **Very high throughput** — millions of jobs/sec across a cluster (use a dedicated broker such as RabbitMQ or Kafka) +- **Existing broker infrastructure** — if Redis/RabbitMQ is already in your stack, a broker-native queue may be simpler +- **Complex routing** — you need topic exchanges, message filtering, or fan-out patterns beyond taskito's pub/sub + + + ## Feature matrix | Feature | taskito | Celery | RQ | Dramatiq | Huey | TaskIQ | |---|---|---|---|---|---|---| | Broker required | **No** | Redis / RabbitMQ | Redis | Redis / RabbitMQ | Redis | Redis / RabbitMQ / Nats | -| Core language | **Rust + ** | Python | Python | Python | Python | Python | +| Core language | **Rust + Python** | Python | Python | Python | Python | Python | | Priority queues | **Yes** | Yes | No | No | Yes | Yes | | Rate limiting | **Yes** | Yes | No | Yes | No | No | | Dead letter queue | **Yes** | No | Yes | No | No | No | @@ -39,25 +60,6 @@ Start with SQLite, scale to Postgres when needed. | Result backend (where task return values are stored) | **Built-in** (SQLite) | Redis / DB / custom | Redis | Redis / custom | Redis / SQLite | Redis / custom | | Setup complexity | **`pip install`** | Broker + backend | Redis server | Broker | Redis server | Broker + backend | -## When to use taskito - -taskito is ideal when: - -- **Single-machine deployments** — no need for distributed workers across multiple servers -- **Zero infrastructure** — you don't want to install, configure, or manage Redis or RabbitMQ -- **Embedded applications** — CLI tools, desktop apps, or services where simplicity matters -- **Prototyping** — get a task queue running in 5 lines, iterate fast -- **Low-to-medium throughput** — hundreds to thousands of jobs per second is plenty - -## When NOT to use taskito - -Consider alternatives when: - -- **Multi-server workers** — you need workers on separate machines (taskito supports this with Postgres/Redis backends, but Celery has more mature distributed tooling) -- **Very high throughput** — millions of jobs/sec across a cluster (use Celery + RabbitMQ) -- **Existing Redis infrastructure** — if Redis is already in your stack, RQ or Huey are simple choices -- **Complex routing** — you need topic exchanges, message filtering, or pub/sub patterns (use Celery + RabbitMQ) - ## Detailed comparison ### vs Celery @@ -144,3 +146,53 @@ async and already have a broker. Choose taskito if you want zero infrastructure. Choose TaskIQ if you're fully async and already have Redis/Nats. + + + + + +## How taskito compares + +Most Node task queues — **BullMQ**, **Bee-Queue**, **Bull** — require a running +Redis instance as the broker, and **Agenda** requires MongoDB. taskito embeds +the queue in a Rust core with a pluggable backend (SQLite, Postgres, or Redis), +so you can start with **zero infrastructure** and add Redis only when you scale +out. + +| | taskito | BullMQ / Bee-Queue | Agenda | +|---|---|---|---| +| **Broker** | None (SQLite) — optional Postgres/Redis | Redis required | MongoDB required | +| **Core** | Rust scheduler | JS + Redis Lua | JS + Mongo | +| **Priorities / rate limits / DLQ** | Built-in | Priorities + rate limits (BullMQ); no built-in DLQ | Limited | +| **Workflows** | Builder DAG, fan-out, sub-workflows, sagas | Flows (BullMQ) | No | +| **Result streaming** | Yes (publish/stream) | No | No | + +**Choose taskito** for a broker-optional queue with a workflow engine built in. +**Choose BullMQ** if Redis is already central to your stack and you want its +mature ecosystem. + + + + + +## How taskito compares + +JVM job schedulers — **Quartz**, **JobRunr**, **Spring Batch** — focus on cron +scheduling and batch orchestration and lean on your existing relational +database. taskito is a general-purpose task queue with a Rust core: priorities, +rate limits, dead-letter queues, workflows, and result streaming, backed by +SQLite, Postgres, or Redis, usable standalone or via the Spring Boot starter. + +| | taskito | Quartz | JobRunr | Spring Batch | +|---|---|---|---|---| +| **Focus** | Task queue + workflows | Cron scheduling | Background jobs | Batch/ETL pipelines | +| **Backend** | SQLite / Postgres / Redis | Your RDBMS | Your RDBMS / Redis | Your RDBMS | +| **Priorities / rate limits / DLQ** | Built-in | No | Limited | No | +| **Workflows** | Builder DAG, fan-out, sub-workflows, sagas | No | No | Step/chunk model | +| **Result streaming** | Yes (publish/stream) | No | No | No | + +**Choose taskito** for a broker-optional queue with priorities, DLQ, and a +workflow engine. **Choose Quartz/Spring Batch** for pure cron scheduling or +chunk-oriented batch processing tightly coupled to Spring. + + From bfc6ed87250d0b75634002007dd555ca449c4be8 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 12 Jul 2026 17:35:31 +0530 Subject: [PATCH 04/12] docs: flag Python-syntax samples in changelog --- docs/content/docs/resources/changelog.mdx | 2 ++ scripts/sync-changelog.mjs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/content/docs/resources/changelog.mdx b/docs/content/docs/resources/changelog.mdx index de352637..54ebe274 100644 --- a/docs/content/docs/resources/changelog.mdx +++ b/docs/content/docs/resources/changelog.mdx @@ -5,6 +5,8 @@ description: "Release history for taskito — every notable change, fix, and fea {/* AUTO-GENERATED from /CHANGELOG.md by scripts/sync-changelog.mjs — do not edit directly. */} +Code snippets below use the Python API for illustration. Every release ships all three SDKs — Python, Node, and Java — in lock-step; see each SDK's API reference for its exact syntax. + All notable changes to taskito are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). All SDKs (Python, Node, Java) and the diff --git a/scripts/sync-changelog.mjs b/scripts/sync-changelog.mjs index fe7611a8..86a45420 100644 --- a/scripts/sync-changelog.mjs +++ b/scripts/sync-changelog.mjs @@ -43,6 +43,8 @@ const frontmatter = [ "", "{/* AUTO-GENERATED from /CHANGELOG.md by scripts/sync-changelog.mjs — do not edit directly. */}", "", + 'Code snippets below use the Python API for illustration. Every release ships all three SDKs — Python, Node, and Java — in lock-step; see each SDK\'s API reference for its exact syntax.', + "", ].join("\n"); writeFileSync(target, `${frontmatter}\n${body}\n`); From fb7d16e42f6f57c3b19f4ae89d2c29baa4873ab5 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 12 Jul 2026 17:42:22 +0530 Subject: [PATCH 05/12] docs: add Python serializers + errors API reference --- .../docs/python/api-reference/errors.mdx | 62 ++++++++++++ .../docs/python/api-reference/index.mdx | 28 ++++++ .../docs/python/api-reference/meta.json | 2 + .../docs/python/api-reference/serializers.mdx | 99 +++++++++++++++++++ 4 files changed, 191 insertions(+) create mode 100644 docs/content/docs/python/api-reference/errors.mdx create mode 100644 docs/content/docs/python/api-reference/serializers.mdx diff --git a/docs/content/docs/python/api-reference/errors.mdx b/docs/content/docs/python/api-reference/errors.mdx new file mode 100644 index 00000000..b6f95208 --- /dev/null +++ b/docs/content/docs/python/api-reference/errors.mdx @@ -0,0 +1,62 @@ +--- +title: Errors +description: "The taskito exception hierarchy — catch by specific type or by the TaskitoError base." +--- + +Almost every exception taskito raises extends `TaskitoError`, so a single +`except` can scope them; the specific subclasses let you branch on what went +wrong. Two exceptions sit outside the tree (noted below) — a blanket +`except TaskitoError` will not catch them. + +```python +from taskito import TaskitoError, ResourceNotFoundError + +try: + result = job.result(timeout=5) +except ResourceNotFoundError as exc: + reregister(exc.args[0]) +except TaskitoError as exc: + report(exc) +``` + +## Hierarchy + +| Class | Extends | Raised when | +|---|---|---| +| `TaskitoError` | `Exception` | Base — never raised directly. | +| `TaskTimeoutError` | `TaskitoError` | A task exceeds its hard timeout. | +| `SoftTimeoutError` | `TaskitoError` | A task exceeds its soft timeout (checked cooperatively via the context). | +| `TaskCancelledError` | `TaskitoError` | A running task detects it has been cancelled. | +| `TaskFailedError` | `TaskitoError` | Awaiting a job that failed or dead-lettered. Carries `errtype`, `traceback`, `job_id`, `raw_error`. | +| `MaxRetriesExceededError` | `TaskitoError` | A task exhausts all retry attempts. Same failure details as `TaskFailedError`. | +| `SerializationError` | `TaskitoError` | Serialization / deserialization or payload-integrity failure (e.g. a bad `SignedSerializer` signature). | +| `CryptoError` | `SerializationError` | A [payload codec](/python/api-reference/serializers) (`HmacCodec`, `AesGcmCodec`) fails to decrypt or verify. | +| `InterceptionError` | `SerializationError` | An [argument interceptor](/python/guides/resources/proxies) rejects or misbehaves. | +| `CircuitBreakerOpenError` | `TaskitoError` | A task's circuit breaker is open. | +| `RateLimitExceededError` | `TaskitoError` | A task's rate limit is exceeded. | +| `JobNotFoundError` | `TaskitoError`, `KeyError` | A job ID isn't found in storage. | +| `QueueError` | `TaskitoError` | A queue-level operational error. | +| `NotesValidationError` | `TaskitoError`, `ValueError` | A `notes` dict breaks the contract (>15 fields, >4 KiB, …). | +| `PredicateRejectedError` | `TaskitoError` | An enqueue-time [predicate](/python/guides/core/predicates) cancelled the submission. Carries `task_name`, `reason`. | +| `BatchPartialFailureError` | `TaskFailedError` | A batch task where some items failed. | +| `ResourceError` | `TaskitoError` | Base for resource dependency-injection errors. | +| `ResourceInitError` | `ResourceError` | A resource factory fails during initialization. | +| `ResourceUnavailableError` | `ResourceError` | A resource is permanently unhealthy and can't be resolved. | +| `CircularDependencyError` | `ResourceError` | Resource dependencies form a cycle. | +| `ResourceNotFoundError` | `ResourceError`, `KeyError` | Resolving a resource name that was never registered. | +| `ProxyReconstructionError` | `ResourceError` | A proxy handler fails to reconstruct an object from its recipe. | +| `ProxyCleanupError` | `ResourceError` | A proxy handler fails during cleanup. | + +## Outside the `TaskitoError` tree + +| Class | Extends | Import from | Raised when | +|---|---|---|---| +| `LockNotAcquiredError` | `Exception` | `taskito.locks` | `queue.lock(...)` can't acquire a held lock. | +| `BatchResultTypeError` | `TypeError` | `taskito` | A batch result is read as the wrong type. | + +The `KeyError` / `ValueError` / `TypeError` mix-ins keep existing +`except KeyError` / `except ValueError` clauses working when these are raised +inside enqueue or lookup paths. + +See [error handling](/python/guides/reliability/error-handling) for retry / +timeout / dead-letter behavior around a failing task. diff --git a/docs/content/docs/python/api-reference/index.mdx b/docs/content/docs/python/api-reference/index.mdx index 5b006c2b..200cbd6d 100644 --- a/docs/content/docs/python/api-reference/index.mdx +++ b/docs/content/docs/python/api-reference/index.mdx @@ -10,9 +10,13 @@ import { ListTodo, CheckCircle2, Variable, + Binary, GitFork, Workflow, + Undo2, + Boxes, TestTube, + AlertTriangle, Terminal, } from "lucide-react"; @@ -50,6 +54,12 @@ signature, parameters, return values, and a short example. href="/python/api-reference/context" description="current_job — progress, logging, cancellation, timeouts inside a task." /> + } + title="Serializers" + href="/python/api-reference/serializers" + description="SmartSerializer, JSON/CBOR, payload codecs, signing and encryption." + /> } title="Canvas" @@ -62,12 +72,30 @@ signature, parameters, return values, and a short example. href="/python/api-reference/workflows" description="The DAG builder — fan-out, fan-in, gates, conditions, sub-workflows." /> + } + title="Saga" + href="/python/api-reference/saga" + description="Compensating workflows — rollback steps that undo completed work on failure." + /> + } + title="Batching" + href="/python/api-reference/batching" + description="Coalesce many enqueues into a single batched job." + /> } title="Testing" href="/python/api-reference/testing" description="test_mode, MockResource, fixtures for synchronous in-process verification." /> + } + title="Errors" + href="/python/api-reference/errors" + description="The TaskitoError exception hierarchy — catch by type or by the base." + /> } title="CLI" diff --git a/docs/content/docs/python/api-reference/meta.json b/docs/content/docs/python/api-reference/meta.json index a27967f8..a2b2f0b7 100644 --- a/docs/content/docs/python/api-reference/meta.json +++ b/docs/content/docs/python/api-reference/meta.json @@ -8,11 +8,13 @@ "task", "result", "context", + "serializers", "canvas", "workflows", "saga", "batching", "testing", + "errors", "cli" ] } diff --git a/docs/content/docs/python/api-reference/serializers.mdx b/docs/content/docs/python/api-reference/serializers.mdx new file mode 100644 index 00000000..8e88dd45 --- /dev/null +++ b/docs/content/docs/python/api-reference/serializers.mdx @@ -0,0 +1,99 @@ +--- +title: Serializers +description: "SmartSerializer, JsonSerializer, CborSerializer, payload codecs, and the Serializer protocol." +--- + +```python +from taskito import Queue, JsonSerializer, CborSerializer +``` + +The Rust core treats payloads as opaque bytes — serialization happens entirely +Python-side. Producers and workers must use a compatible serializer. + +## Built-ins + +| Class | Description | +|---|---| +| `SmartSerializer` | **Default.** MsgPack for plain data, transparent cloudpickle fallback for anything MsgPack can't encode (lambdas, closures, class instances). A one-byte tag records which codec produced each payload; also reads CBOR (`0x02`) wire payloads from another SDK. | +| `CloudpickleSerializer` | Pure cloudpickle — handles any picklable Python object. Same-language only (not cross-SDK). | +| `JsonSerializer` | Human-readable JSON bytes. Simple, cross-language, MsgPack-native types only. | +| `MsgPackSerializer` | Compact binary (MsgPack). MsgPack-native types only. | +| `CborSerializer` | Binary CBOR (RFC 8949), tagged with the `0x02` wire-envelope byte — the format for tasks produced or consumed by another Taskito SDK. Round-trips big integers, `datetime` (tz-aware), `bytes`, and `Decimal` losslessly. | +| `SignedSerializer` | Wraps another serializer with an HMAC-SHA256 tag; the worker refuses to deserialize bytes not produced with the shared key. | +| `EncryptedSerializer` | Wraps another serializer with AES-256-GCM; confidentiality **and** integrity. | + +Set one queue-wide, or override per task: + +```python +queue = Queue(serializer=JsonSerializer()) + +@queue.task(serializer=CborSerializer()) +def cross_sdk_job(payload: dict) -> None: ... +``` + + + `SmartSerializer` is the default because it is fast for the common case yet + never fails on an exotic Python object. Reach for `CborSerializer` only when a + task is produced or consumed by another SDK. + + +## Signing and encryption + +`SignedSerializer(inner, key)` and `EncryptedSerializer(inner, key)` both wrap a +delegate serializer. `key` must be at least 32 bytes of CSPRNG output, shared by +every producer and worker. Signing authenticates (prevents a storage writer from +smuggling code into a cloudpickle payload); encryption also gives +confidentiality. + +```python +import os +from taskito import Queue, SignedSerializer, SmartSerializer + +key = os.urandom(32) # share across producers and workers +queue = Queue(serializer=SignedSerializer(SmartSerializer(), key)) +``` + +## Payload codecs + +A `PayloadCodec` is a reversible byte transform layered *around* a serializer — +compression, encryption, signing — rather than a replacement for it. Codecs +compose: a chain encodes in list order on the producer and decodes in reverse on +the worker. Wire formats are part of the cross-SDK contract, so a codec-framed +payload decodes from any Taskito SDK. + +| Class | Wire format | Description | +|---|---|---| +| `GzipCodec(max_decompressed_bytes=...)` | standard gzip stream | Compresses; decompression capped at 64 MiB by default (zip-bomb guard). | +| `HmacCodec(key)` | `[32-byte mac][body]` | HMAC-SHA256 signs; rejects tampered or wrong-key payloads. | +| `AesGcmCodec(key)` | `[12-byte IV][ciphertext \|\| 16-byte tag]` | AES-128/192/256-GCM (by key length); confidentiality + integrity. | + +Apply a chain queue-wide with `Queue(codec=...)` (wraps the serializer, covers +every payload and result), or register named codecs via `Queue(codecs=...)` and +opt individual tasks in with `@queue.task(codecs=[...])` (payload only — results +still use the plain serializer): + +```python +from taskito import Queue, GzipCodec, HmacCodec + +queue = Queue(codec=[GzipCodec(), HmacCodec(hmac_key)]) # gzip, then hmac +``` + + + `CodecSerializer(delegate, codecs)` is the internal `Serializer` that layers a + codec chain around a delegate — `Queue(codec=...)` builds one for you, so you + rarely construct it directly. Nondeterministic codecs (e.g. `AesGcmCodec`) make + a payload's bytes non-reproducible, so avoid pairing them with content-hash + idempotency. + + +## `Serializer` protocol + +```python +class Serializer(Protocol): + def dumps(self, obj: Any) -> bytes: ... + def loads(self, data: bytes) -> Any: ... +``` + +Any object with `dumps`/`loads` is a valid serializer. See the +[Serializers guide](/python/guides/extensibility/serializers) for a custom +implementation and the format-selection tradeoffs. From c39b5c48eeded656ee8407d44701ae97c60aac02 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 12 Jul 2026 17:42:23 +0530 Subject: [PATCH 06/12] docs: add Node testing API reference --- .../content/docs/node/api-reference/index.mdx | 1 + .../content/docs/node/api-reference/meta.json | 2 +- .../docs/node/api-reference/testing.mdx | 52 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 docs/content/docs/node/api-reference/testing.mdx diff --git a/docs/content/docs/node/api-reference/index.mdx b/docs/content/docs/node/api-reference/index.mdx index 0258b130..3ac3ecba 100644 --- a/docs/content/docs/node/api-reference/index.mdx +++ b/docs/content/docs/node/api-reference/index.mdx @@ -15,5 +15,6 @@ The public API exported from the `taskito` barrel. Contrib helpers live under | [Context](/node/api-reference/context) | `currentJob()` — signal, progress, ids. | | [Serializers](/node/api-reference/serializers) | `JsonSerializer`, `MsgpackSerializer`, `CborSerializer`, the `Serializer` interface. | | [Workflows](/node/api-reference/workflows) | The workflow builder + run handle. | +| [Testing](/node/api-reference/testing) | `mockResource` — stub an injected resource in tests. | | [Errors](/node/api-reference/errors) | The `TaskitoError` hierarchy. | | [CLI](/node/api-reference/cli) | The `taskito` command. | diff --git a/docs/content/docs/node/api-reference/meta.json b/docs/content/docs/node/api-reference/meta.json index a8692ac6..9a42889d 100644 --- a/docs/content/docs/node/api-reference/meta.json +++ b/docs/content/docs/node/api-reference/meta.json @@ -1 +1 @@ -{ "title": "API Reference", "root": true, "pages": ["index", "queue", "task", "worker", "result", "context", "serializers", "workflows", "errors", "cli"] } +{ "title": "API Reference", "root": true, "pages": ["index", "queue", "task", "worker", "result", "context", "serializers", "workflows", "testing", "errors", "cli"] } diff --git a/docs/content/docs/node/api-reference/testing.mdx b/docs/content/docs/node/api-reference/testing.mdx new file mode 100644 index 00000000..ca6f40ef --- /dev/null +++ b/docs/content/docs/node/api-reference/testing.mdx @@ -0,0 +1,52 @@ +--- +title: Testing +description: "mockResource — swap a real dependency for a stub in tests." +--- + +```ts +import { mockResource } from "@byteveda/taskito"; +import type { MockResource } from "@byteveda/taskito"; +``` + +The Node SDK's testing model is: run a real in-process worker against a +throwaway queue (a temp SQLite file), enqueue, and await the result — the same +API you run in production. See the [testing guide](/node/guides/operations/testing) +for the full worker-in-a-test harness. This page covers the one dedicated test +utility: `mockResource`, for swapping an injected [resource](/node/api-reference/queue/resources) +for a stub. + +## `mockResource` + +```ts +function mockResource(value: T): MockResource; +``` + +Builds a [`MockResource`](#mockresource-1) wrapping `value`. Register its +`factory` with `queue.resource(name, mock.factory)` in place of the real +factory, then assert on `mock.resolutions` to confirm the resource was built. + +```ts +import { mockResource } from "@byteveda/taskito"; + +const db = mockResource({ query: async () => [{ id: 1 }] }); +queue.resource("db", db.factory); + +// ...run the task under test... + +expect(db.resolutions).toBe(1); // the worker built the resource exactly once +``` + +## `MockResource` + +```ts +interface MockResource { + value: T; // the value the factory returns + factory: () => T; // pass to queue.resource(name, mock.factory) + resolutions: number; // how many times the factory was invoked +} +``` + +`resolutions` increments each time the injected factory runs, so a test can +assert a resource was (or wasn't) constructed — useful for verifying scope +behavior, e.g. a `worker`-scoped resource builds once while a `task`-scoped one +builds per job. From a2a21e97446778fc4290f1f242e5598b196c8059 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 12 Jul 2026 17:47:18 +0530 Subject: [PATCH 07/12] docs: document Java task-log streaming in reference --- docs/content/docs/java/api-reference/queue/events.mdx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/content/docs/java/api-reference/queue/events.mdx b/docs/content/docs/java/api-reference/queue/events.mdx index b81d61ae..f2f7aa48 100644 --- a/docs/content/docs/java/api-reference/queue/events.mdx +++ b/docs/content/docs/java/api-reference/queue/events.mdx @@ -5,9 +5,17 @@ description: "Structured task logs, webhooks, and the autoscaler." ## Logs +Durable, per-job structured log rows in shared storage. Beyond diagnostics they +carry **streaming partial results**: a running task writes partials at level +`"result"` and a consumer polls them live (see the +[streaming guide](/java/guides/core/streaming)). + | Method | Description | |---|---| -| `writeTaskLog(...)` / `getTaskLogs(id)` | Structured task-log lines for a job. | +| `writeTaskLog(jobId, taskName, level, message)` | Append a log row. An overload adds a String `extra` payload. | +| `getTaskLogs(jobId)` → `List` | Every row for a job, oldest first. | +| `getTaskLogsAfter(jobId, afterId)` → `List` | Rows after a cursor id — poll this to stream new partials without rescanning. | +| `queryTaskLogs(taskName, level, sinceMs, limit)` → `List` | Cross-job query by task and level (e.g. every `"result"` line since a timestamp). | ## Webhooks & autoscaler From 8fe88ab79e1eacee7430cce29f31cd2f0248aa7d Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 12 Jul 2026 17:47:18 +0530 Subject: [PATCH 08/12] docs: document Java circuit breakers in reference --- .../docs/java/api-reference/queue/workers.mdx | 6 ++++ docs/content/docs/java/api-reference/task.mdx | 31 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/docs/content/docs/java/api-reference/queue/workers.mdx b/docs/content/docs/java/api-reference/queue/workers.mdx index e5828a75..3284f858 100644 --- a/docs/content/docs/java/api-reference/queue/workers.mdx +++ b/docs/content/docs/java/api-reference/queue/workers.mdx @@ -16,6 +16,12 @@ description: "Worker builder, middleware, and workflow control." |---|---| | `use(Middleware)` | Cross-cutting hooks — `onEnqueue`, `before`/`after`/`onError`, and the outcome hooks. | +## Circuit breakers + +| Method | Description | +|---|---| +| `listCircuitBreakers()` → `List` | Live state of every registered [circuit breaker](/java/api-reference/task#circuitbreakerconfig) — open/closed/half-open, failure counts, next-probe time. | + ## Workflows | Method | Description | diff --git a/docs/content/docs/java/api-reference/task.mdx b/docs/content/docs/java/api-reference/task.mdx index b4129b61..f9875cbf 100644 --- a/docs/content/docs/java/api-reference/task.mdx +++ b/docs/content/docs/java/api-reference/task.mdx @@ -30,6 +30,7 @@ enqueue options. The fluent option methods each return a new descriptor. | `delayMs(long)` / `delay(Duration)` | Schedule after a delay. | | `retryPolicy(RetryPolicy)` | Backoff curve — registered with the worker on `start()`. | | `codecs(String...)` | Named [payload codecs](/java/api-reference/serializers) applied to this task. | +| `circuitBreaker(CircuitBreakerConfig)` | Trip the task after repeated failures; the worker registers the breaker on `start()`. | | `withOptions(EnqueueOptions)` | Replace the default options wholesale. | ```java @@ -45,7 +46,9 @@ Task sendEmail = Task.of("send_email", EmailPayload.class) Per-enqueue overrides, built with `EnqueueOptions.builder()`; unset fields take core defaults. Fields: `queue`, `priority`, `maxRetries`, `timeoutMs` / `timeout(Duration)`, `delayMs` / `delay(Duration)`, `uniqueKey` (alias -`jobId`) for idempotent enqueues, `metadata`, `namespace`. +`jobId`) for idempotent enqueues, `metadata`, `namespace`, and +`dependsOn(String... jobIds)` (this job stays pending until the listed jobs +finish — see [enqueue options](/java/guides/core/enqueue-options#job-dependencies)). ```java taskito.enqueue(sendEmail, payload, EnqueueOptions.builder() @@ -64,6 +67,32 @@ taskito.enqueue(sendEmail, payload, EnqueueOptions.builder() The retry *budget* comes from `maxRetries`; the policy only supplies the timing, which the core scheduler applies durably. +## `CircuitBreakerConfig` + +Guards a task: after `threshold` failures inside the rolling `window`, the +breaker opens and the task is skipped for `cooldown` before probing again. +Attach with `task.circuitBreaker(config)`; the worker registers it on `start()`. + +| Factory / method | Description | +|---|---| +| `CircuitBreakerConfig.of(int threshold)` | Open after `threshold` failures; other fields default. | +| `CircuitBreakerConfig.builder(int threshold)` | Builder for the full config. | +| `window(Duration)` / `windowSeconds(long)` | Rolling failure window. | +| `cooldown(Duration)` / `cooldownSeconds(long)` | How long the breaker stays open. | +| `halfOpenProbes(int)` | Trial jobs admitted while half-open. | +| `halfOpenSuccessRate(double)` | Success fraction among probes needed to close. | + +```java +Task charge = Task.of("charge", ChargePayload.class) + .circuitBreaker(CircuitBreakerConfig.builder(5) + .window(Duration.ofMinutes(1)) + .cooldown(Duration.ofSeconds(30)) + .build()); +``` + +Inspect live breaker state with `taskito.listCircuitBreakers()` +([workers & hooks](/java/api-reference/queue/workers#circuit-breakers)). + ## Annotations `@TaskHandler` marks a method as a task handler. A compile-time processor From 6f2470696807ff2853bbcf20bb85ad00318d2356 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 12 Jul 2026 17:47:18 +0530 Subject: [PATCH 09/12] docs: add Java job-dependencies guide section --- .../docs/java/guides/core/enqueue-options.mdx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/content/docs/java/guides/core/enqueue-options.mdx b/docs/content/docs/java/guides/core/enqueue-options.mdx index 08229156..9f5b8f9c 100644 --- a/docs/content/docs/java/guides/core/enqueue-options.mdx +++ b/docs/content/docs/java/guides/core/enqueue-options.mdx @@ -26,6 +26,7 @@ taskito.enqueue(sendEmail, payload, EnqueueOptions.builder() | `uniqueKey` | Idempotency key — a duplicate enqueue is a no-op while the first job is pending/running. | | `metadata` | Free-form string stored with the job (surfaces on the dashboard / inspection). | | `namespace` | Partition the store — jobs are only visible to clients/workers in the same namespace. | +| `dependsOn(String... jobIds)` | Gate this job on other jobs — it stays pending until they all finish ([below](#job-dependencies)). | `toBuilder()` derives a modified copy from an existing instance, and the fluent `Task` methods ([tasks](/java/guides/core/tasks#per-task-defaults)) cover the @@ -57,3 +58,20 @@ List ids = taskito.enqueueMany(resize, List.of(a, b, c), A `uniqueKey` on the batch dedupes there too: a payload whose key already has an active job resolves to the existing job's id instead of failing the batch. `enqueueAll` is an alias of `enqueueMany`. + +## Job dependencies + +`dependsOn(jobId...)` makes a job wait for other jobs before it becomes eligible +to run — a lightweight per-job DAG, distinct from the +[workflow builder](/java/guides/workflows). The job stays pending until every +listed job completes successfully. + +```java +String extract = taskito.enqueue(extractTask, source); +String transform = taskito.enqueue(transformTask, config, + EnqueueOptions.builder().dependsOn(extract).build()); +``` + +Enqueue is rejected if a listed job id is missing or already dead, cancelled, or +failed. If a dependency later fails or is cancelled, the dependent is cancelled +too — the cancellation cascades down the chain. From 308fc71af9988db9e1ae3f7a91b3533d1e775092 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 12 Jul 2026 21:12:39 +0530 Subject: [PATCH 10/12] docs: clarify Java depends_on completion semantics --- docs/content/docs/java/api-reference/task.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/docs/java/api-reference/task.mdx b/docs/content/docs/java/api-reference/task.mdx index f9875cbf..f81627d8 100644 --- a/docs/content/docs/java/api-reference/task.mdx +++ b/docs/content/docs/java/api-reference/task.mdx @@ -48,7 +48,8 @@ core defaults. Fields: `queue`, `priority`, `maxRetries`, `timeoutMs` / `timeout(Duration)`, `delayMs` / `delay(Duration)`, `uniqueKey` (alias `jobId`) for idempotent enqueues, `metadata`, `namespace`, and `dependsOn(String... jobIds)` (this job stays pending until the listed jobs -finish — see [enqueue options](/java/guides/core/enqueue-options#job-dependencies)). +complete successfully; a failed or cancelled dependency cancels it — see +[enqueue options](/java/guides/core/enqueue-options#job-dependencies)). ```java taskito.enqueue(sendEmail, payload, EnqueueOptions.builder() From 02b07558cdf7aa79ca6acff46a14da85ea6ca5bd Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 12 Jul 2026 21:12:40 +0530 Subject: [PATCH 11/12] docs: fix ResourceNotFoundError example message --- docs/content/docs/python/api-reference/errors.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/docs/python/api-reference/errors.mdx b/docs/content/docs/python/api-reference/errors.mdx index b6f95208..3bf7bde3 100644 --- a/docs/content/docs/python/api-reference/errors.mdx +++ b/docs/content/docs/python/api-reference/errors.mdx @@ -14,7 +14,8 @@ from taskito import TaskitoError, ResourceNotFoundError try: result = job.result(timeout=5) except ResourceNotFoundError as exc: - reregister(exc.args[0]) + # the message names the missing resource, e.g. "Resource 'db' is not registered" + print(exc) except TaskitoError as exc: report(exc) ``` From 383cb8d8e3777e782b8c5331ed587e1ab8a68333 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 12 Jul 2026 21:12:40 +0530 Subject: [PATCH 12/12] docs: separate broker from backend in comparison --- docs/content/docs/resources/comparison.mdx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/content/docs/resources/comparison.mdx b/docs/content/docs/resources/comparison.mdx index 6e337fd0..870e68c7 100644 --- a/docs/content/docs/resources/comparison.mdx +++ b/docs/content/docs/resources/comparison.mdx @@ -4,8 +4,8 @@ description: "Taskito vs other task queues — feature matrix and decision guide --- **TL;DR**: Taskito is without the -broker (a separate server such as Redis or RabbitMQ that holds pending jobs). Rust scheduler, no Redis/RabbitMQ, lower latency, better concurrency. -Start with SQLite, scale to Postgres when needed. +broker (a separate server such as Redis or RabbitMQ that holds pending jobs). Rust scheduler, no broker required, lower latency, better concurrency. +Start with SQLite; add Postgres or Redis as a backend when you scale out. ## When to use taskito @@ -177,11 +177,12 @@ mature ecosystem. ## How taskito compares -JVM job schedulers — **Quartz**, **JobRunr**, **Spring Batch** — focus on cron -scheduling and batch orchestration and lean on your existing relational -database. taskito is a general-purpose task queue with a Rust core: priorities, -rate limits, dead-letter queues, workflows, and result streaming, backed by -SQLite, Postgres, or Redis, usable standalone or via the Spring Boot starter. +JVM background-job tools each specialize: **Quartz** for cron scheduling, +**JobRunr** for background jobs (RDBMS or Redis), **Spring Batch** for +batch/ETL pipelines. taskito is a general-purpose task queue with a Rust core: +priorities, rate limits, dead-letter queues, workflows, and result streaming, +backed by SQLite, Postgres, or Redis, usable standalone or via the Spring Boot +starter. | | taskito | Quartz | JobRunr | Spring Batch | |---|---|---|---|---|