Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9eadad2
feat(node): OpenTelemetry tracing middleware
pratyush618 Jun 20, 2026
cfb70d7
feat(node): Prometheus metrics middleware + stats collector
pratyush618 Jun 20, 2026
65745c7
refactor(node): extract createDashboardHandler for mounting
pratyush618 Jun 20, 2026
087168c
feat(node): Express integration (REST router + dashboard mount)
pratyush618 Jun 20, 2026
1f61873
feat(node): Fastify integration (REST router + dashboard mount)
pratyush618 Jun 20, 2026
46e74b7
feat(node): NestJS module + injectable TaskitoService
pratyush618 Jun 20, 2026
1911b78
fix(node): add prom-client to devDependencies
pratyush618 Jun 20, 2026
73128e4
docs(node): document contrib integrations
pratyush618 Jun 20, 2026
276743c
feat(node): Sentry error-reporting middleware
pratyush618 Jun 20, 2026
8b742d2
docs(node): document Sentry contrib middleware
pratyush618 Jun 20, 2026
1a3e06b
feat(node): workflow step conditions (on_success/on_failure/always)
pratyush618 Jun 20, 2026
a22fc9e
feat(node): workflow approval gates
pratyush618 Jun 20, 2026
9166449
feat(node): nested sub-workflows
pratyush618 Jun 20, 2026
8a6e5eb
feat(node): saga compensation on workflow failure
pratyush618 Jun 20, 2026
ca6ace4
test(node): group tests by feature area (Python-style)
pratyush618 Jun 20, 2026
22a2481
chore: add Node SDK lint + typecheck pre-commit hooks
pratyush618 Jun 20, 2026
03d7b50
docs(node): document workflow conditions/gates/sub-workflows/saga
pratyush618 Jun 20, 2026
e079e29
fix(node): guard contrib query flattening against prototype pollution
pratyush618 Jun 20, 2026
33df47f
fix(node): build query map via Object.fromEntries, no dynamic write
pratyush618 Jun 20, 2026
c2beced
fix(node): cancel compensation job if node binding fails
pratyush618 Jun 20, 2026
f486825
fix(node): don't leak internal errors from express router 500s
pratyush618 Jun 20, 2026
0a39cbf
fix(node): validate prometheus collector interval
pratyush618 Jun 20, 2026
e2c9598
fix(node): reject mutually exclusive include/exclude routes
pratyush618 Jun 20, 2026
017dc8d
test(node): guaranteed teardown for fastify/nest, route-option test
pratyush618 Jun 20, 2026
a502508
docs(node): clarify gate rejection branches, note webhook auth
pratyush618 Jun 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ repos:
types: [python]
pass_filenames: false

- id: node-lint
name: node lint (biome)
entry: bash -c 'cd sdks/node && pnpm exec biome ci src test'
language: system
files: ^sdks/node/(src/|test/|biome\.json)
pass_filenames: false

- id: node-typecheck
name: node typecheck (tsc)
entry: bash -c 'cd sdks/node && pnpm exec tsc --noEmit -p tsconfig.json'
language: system
files: ^sdks/node/(src/|test/|tsconfig\.json|package\.json)
pass_filenames: false

- id: dashboard-lint
name: dashboard lint (biome)
entry: bash -c 'cd dashboard && pnpm exec biome ci src/'
Expand Down
87 changes: 43 additions & 44 deletions NODE_SDK_TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ SDK, zero Python dependency, over the shared Rust core.
| **Periodic + circuit-breaker** | `a4cc9a4` | `queue.registerPeriodic`; per-task `circuitBreaker` config |
| **Workflows: fan-out / fan-in** | (this branch) | TS `WorkflowTracker` brain (`src/workflows/tracker.ts`) driven by the outcome callback; `.fanOut()` / `.fanIn()` builder steps; napi primitives `expandFanOut`/`checkFanOutCompletion`/`createDeferredJob`/`finalizeRunIfTerminal`/`getWorkflowRunPlan`/`workflowNodeForJob`/`cascadeSkipPending`; deferred-node submit. Storage-reconstructable (no submit-time registration) |
| **Dashboard DAG completeness** | (this branch) | `JsWorkflowNode` carries `fanOutCount` + `compensation_*`; `/dag` handler enriches the raw graph with per-node `deps[]` + live `status` + job-id `id` so the SPA visualizer renders edges/colours/links |
| **Contrib integrations** | (this branch) | `src/contrib/{otel,prometheus,express,fastify,nest,sentry}.ts` — each an optional subpath export (`taskito/contrib/*`) with an optional peer dep. OTel + Prometheus + Sentry = `Middleware` over the events layer; Express/Fastify = REST router (shared `rest.ts` table) + dashboard mount (via extracted `createDashboardHandler`); Nest = `TaskitoModule.forRoot` + injectable `TaskitoService`. 17 new vitest tests |
| **Workflows: conditions / gates / sub-workflows / saga** | (this branch) | `WorkflowTracker` drives all deferred kinds. Conditions (`on_success`/`on_failure`/`always`), approval gates (`.gate`, `resolveGate`/`approveGate`/`rejectGate`, timeouts), nested sub-workflows (`.subWorkflow`, parent linkage + storage-driven child→parent resolve), saga compensation (`.step({compensate})`, reverse-dependency storage-driven rollback). `StepMetadata` + napi (`skip`/gate/sub-workflow/saga) extended. 12 new tests |

**Verify everything green:**
```bash
Expand Down Expand Up @@ -68,48 +70,44 @@ context managers) — do **not** port 1:1. Design Node-native equivalents:
**Effort:** large (design-heavy). **Recommendation:** scope a minimal resource DI
first (worker-scoped singletons + task-scoped factories); defer proxies entirely.

### 2. Contrib integrations — MEDIUM

Python ships `contrib/` (OpenTelemetry, Sentry, Prometheus, Flask, Django,
FastAPI). Node equivalents:

- **Observability:** OpenTelemetry (`@opentelemetry/api`), Prometheus
(`prom-client`) — implement as middleware over the existing events/middleware
layer (`src/middleware.ts`). Each = a `Middleware` that records spans/metrics.
- **Web frameworks:** Express / Fastify / Nest helpers (enqueue from a request,
mount the dashboard). Python's Flask/Django/FastAPI map to these.

**Where:** new `sdks/node/src/contrib/` (one file per integration, optional peer
deps). **Effort:** medium. Each integration is small and independent → **one
commit each**.

### 3. Advanced workflow features — LARGE (fan-out DONE; gates / sub-workflows / saga remain)

DAG/linear + **fan-out / fan-in** work. The tracker-brain foundation is built:
a TS `WorkflowTracker` (`src/workflows/tracker.ts`) reacts to the worker outcome
callback, reconstructs the run plan from storage (DAG + step metadata — no
submit-time registration), and drives on-demand orchestration via napi
primitives (`expandFanOut`, `checkFanOutCompletion`, `createDeferredJob`,
`finalizeRunIfTerminal`, `getWorkflowRunPlan`, `workflowNodeForJob`,
`cascadeSkipPending`). `submit_workflow` takes `deferredNodeNames` (fan-out /
fan-in ∪ descendants get a node but no static job). Failures are fail-fast.

**Still unbound: gates/conditions, sub-workflows, saga compensation.** They build
on this same foundation (the Python references are
`crates/taskito-python/src/py_queue/workflow_ops/{gates,saga}.rs` + the Python
`WorkflowTracker`):
- **gates/conditions** — a deferred node enters `waiting_approval`; resolve via a
new `resolveWorkflowGate` napi method + a JS-side timer (`setTimeout`) for
timeouts. Conditions = a `should_execute` check in `evaluateSuccessors`.
- **sub-workflows** — extend `submit_workflow` with `parent_run_id` /
`parent_node_name`; the tracker submits a child run for a sub-workflow node and
resolves the parent node when the child finalizes (populates `/children`).
- **saga** — reverse-topo compensation waves driven by the tracker; needs the
`setWorkflowNodeCompensation*` + run-state napi setters bound (storage methods
already exist on the `WorkflowStorage` trait).

**Where:** `crates/taskito-node/src/queue/workflows.rs` + `src/workflows/tracker.ts`.
**Effort:** medium each now the brain exists. Each is a separate feature/commit.
### 2. Contrib integrations — DONE

Shipped: OpenTelemetry + Prometheus + Sentry middleware, Express + Fastify (REST router +
dashboard mount), and a NestJS module
(`src/contrib/{otel,prometheus,sentry,express,fastify,nest}.ts`). Each is an optional
`taskito/contrib/*` subpath export with an optional peer dependency, its own tsup entry,
and tests. The REST logic is shared via a framework-neutral `src/contrib/rest.ts` route
table; the dashboard mount reuses `createDashboardHandler` (extracted from
`dashboard/server.ts`). NestJS uses an explicit `@Inject(TASKITO_QUEUE)` token to avoid
relying on esbuild decorator-metadata emission (`experimentalDecorators` on; biome
`unsafeParameterDecoratorsEnabled`). Sentry captures the real exception from `onError`
and reports it on dead-letter (one event/dead job), optional per-retry warnings.

### 3. Advanced workflow features — DONE

The TS `WorkflowTracker` (`src/workflows/tracker.ts`) reconstructs the run plan from
storage and drives every deferred step kind off the worker outcome stream:
- **fan-out / fan-in** — `expandFanOut` / `checkFanOutCompletion` + collector.
- **conditions** — `.step({ condition })` (`on_success`/`on_failure`/`always`);
`shouldExecute` gates dispatch in `evaluateSuccessors`, skips propagate via
`skipWorkflowNode`. (String predicates only — JS callables can't cross the
storage-reconstructable boundary.)
- **gates** — `.gate({ timeoutMs, onTimeout, message })` parks at `waiting_approval`;
`setWorkflowNodeWaitingApproval` + `resolveWorkflowGate` napi, JS `setTimeout`
(unref'd) for timeouts; `queue.workflows.resolveGate`/`approveGate`/`rejectGate`
resolve from any process (idempotent via a terminal-status guard).
- **sub-workflows** — `.subWorkflow({ workflow })`; child spec persisted as base64
`SubWorkflowTransport` in `sub_workflow` metadata; `submit_workflow` gained
`parent_run_id`/`parent_node_name`; child→parent resolution is storage-driven
(read parent linkage on child finalize → `resolveWorkflowGate`). Nesting supported.
- **saga** — `.step({ compensate })`; on a failed run the tracker re-derives the next
compensable nodes from node status each rollback outcome (reverse-dependency,
storage-driven, no in-memory waves), enqueues idempotent rollback jobs
(`enqueueCompensation`, dedup `unique_key`), routes their outcomes via
`compensationNodeForJob`, and finalizes `compensated` / `compensation_failed`.

`StepMetadata` (taskito-workflows crate) gained `gate` / `sub_workflow` / `compensate`
fields (JSON blob, no schema migration; `#[derive(Default)]` + spread literals).

### 4. Dashboard workflows DAG panel completeness — DONE

Expand Down Expand Up @@ -239,7 +237,7 @@ pnpm run build:native # napi build, all features
pnpm run build:ts # tsup dual ESM/CJS + .d.ts
pnpm typecheck # tsc --noEmit (includes test/)
pnpm lint # biome
pnpm test # vitest (48 tests)
pnpm test # vitest (81 tests)
pnpm run build:dashboard # build the React SPA into static/dashboard
```

Expand All @@ -261,8 +259,9 @@ sdks/node/src/
webhooks/{store,deliverer,manager,types,index}.ts
serializers/{serializer,json,msgpack,index}.ts
dashboard/{server,routes,handlers,contract,static,metrics,api,index}.ts
contrib/{otel,prometheus,sentry,express,fastify,nest,rest}.ts # optional subpath exports
cli/{index,connect,output,commands/*}.ts
sdks/node/test/*.test.ts # grouped by feature area
sdks/node/test/{core,worker,observability,integrations,workflows,dashboard}/*.test.ts # grouped by feature area
```

Memory: see `.claude/memory/session-history.md` (Node SDK section) for the running
Expand Down
Loading