feat(todo-worker): add todo worker quickstart template#9
Conversation
Node.js/TypeScript CRUD worker using the iii SDK, designed as a cloneable starting point for building iii workers. - 5 HTTP endpoints: create, list, get, update, delete todos - In-memory store, layered structure (store/handlers/hooks/index) - Multi-stage Dockerfile for production builds - Makefile with build/run/dev/clean targets - Added to worker registry as OCI image
📝 WalkthroughWalkthroughThis pull request introduces a new Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant HttpTrigger as HTTP Trigger
participant Handler as Handler Function
participant Logger
participant Store as TodoStore
participant ApiResponse as ApiResponse
Client->>HttpTrigger: POST /todos {"title": "Buy milk"}
HttpTrigger->>Handler: createTodo(req, logger)
Handler->>Store: create(title)
Store->>Store: Generate UUID, timestamp
Store-->>Handler: Return Todo
Handler->>Logger: Log creation
Handler->>ApiResponse: Return 201 + Todo
ApiResponse-->>Client: 201 Created
Client->>HttpTrigger: GET /todos
HttpTrigger->>Handler: listTodos(req, logger)
Handler->>Store: list()
Store-->>Handler: Return Todo[]
Handler->>Logger: Log count
Handler->>ApiResponse: Return 200 + TodoList
ApiResponse-->>Client: 200 OK
Client->>HttpTrigger: PUT /todos/:id {"completed": true}
HttpTrigger->>Handler: updateTodo(req, logger)
Handler->>Store: update(id, data)
Store-->>Handler: Return Updated Todo
Handler->>ApiResponse: Return 200 + Todo
ApiResponse-->>Client: 200 OK
Client->>HttpTrigger: DELETE /todos/:id
HttpTrigger->>Handler: deleteTodo(req, logger)
Handler->>Store: remove(id)
Store-->>Handler: Return true
Handler->>Logger: Log deletion
Handler->>ApiResponse: Return 204 No Content
ApiResponse-->>Client: 204 No Content
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
image-resize/example/config.yaml (1)
66-66: Prefer a pinned image tag/digest over:latest.Line 66 uses a mutable tag, which hurts reproducibility and rollback safety for templates.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@image-resize/example/config.yaml` at line 66, Replace the mutable image tag used in the image field ("docker.io/andersonofl/image-resize:latest") with an immutable reference: either a specific versioned tag (e.g., v1.2.3) or an image digest (sha256:...), so update the image value to "docker.io/andersonofl/image-resize:<version>" or "docker.io/andersonofl/image-resize@sha256:<digest>" to ensure reproducible deployments and safe rollbacks.todo-worker/iii.worker.yaml (1)
17-17: Prefernpm cifor deterministic installs in worker setup scripts.Line 17 uses
npm install, which can drift across environments;npm ciis safer when a lockfile exists.Suggested change
scripts: - install: "npm install" + install: "npm ci" start: "npm run dev"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@todo-worker/iii.worker.yaml` at line 17, Replace the non-deterministic install command in the worker setup by updating the install key in iii.worker.yaml (the "install" field) from "npm install" to "npm ci" so CI and local builds use the lockfile for deterministic installs; ensure the repo contains a lockfile (package-lock.json or npm-shrinkwrap.json) before changing the "install" value.registry/index.json (1)
28-37: Pin the image digest to improve reproducibility.The image reference lacks a manifest digest. OCI best practice is to pin deployments using tag+digest format (
docker.io/andersonofl/todo-worker:0.1.0@sha256:<hash>) so the exact artifact is always pulled, rather than relying on the mutablelatesttag which can drift over time.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@registry/index.json` around lines 28 - 37, The image entry in registry/index.json is not pinned to a manifest digest; update the "image" field to use tag+digest format using the "version" value (e.g., docker.io/andersonofl/todo-worker:0.1.0@sha256:<hash>) so the exact artifact is referenced; obtain the correct sha256 digest from the registry (docker pull or registry API) and replace the current "image" string, and then either remove or update the "latest" field to reflect the pinned digest to avoid relying on the mutable tag — change the "image" key and adjust "latest" and/or "version" entries accordingly for the todo-worker entry.todo-worker/src/handlers.ts (1)
8-27: Consider trimming title to prevent whitespace-only todos.The
!titlecheck catches empty strings but allows whitespace-only titles (e.g.," "). For a quickstart template this is acceptable, but you may want to add.trim()for stricter validation.💡 Optional: Add trim validation
async function createTodo(req: ApiRequest, logger: Logger): Promise<HandlerResponse> { const { title } = req.body as { title?: string } + const trimmedTitle = title?.trim() - if (!title) { + if (!trimmedTitle) { return { status_code: 400, body: { error: 'Missing "title" field' }, headers: { 'Content-Type': 'application/json' }, } } - const todo = store.create(title) + const todo = store.create(trimmedTitle) logger.info('Todo created', { id: todo.id, title })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@todo-worker/src/handlers.ts` around lines 8 - 27, In createTodo, trim the incoming title before validation and use the trimmed value for creation and logging: replace checks against raw title with a trimmedTitle = title?.trim(), return the 400 error if trimmedTitle is falsy, call store.create(trimmedTitle) and log the trimmedTitle (refer to createTodo, logger.info, and store.create) so purely-whitespace titles are rejected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@image-resize/example/config.yaml`:
- Around line 67-68: The config.yaml entry "config: III_ENGINE_URL" is
ineffective because the image-resize binary (main.rs) only accepts the engine
URL via the CLI flag "--url" or the environment variable "III_URL"; remove the
"config: III_ENGINE_URL" field and instead supply the URL using your
orchestrator's mechanism — e.g., add an env var "III_URL" with the ws:// value
or pass the CLI arg "--url ws://..." (matching the Dockerfile CMD pattern) so
the image-resize binary actually receives the engine URL.
In `@todo-worker/Dockerfile`:
- Around line 11-17: The Dockerfile is running the container as root; create and
use a non-root user by adding a dedicated unprivileged user and group, chown the
WORKDIR (/app) and any copied files (e.g., dist) to that user, and add a USER
<username> line before CMD to switch to that user; reference the Dockerfile
directives WORKDIR, COPY --from=build ./dist, RUN npm ci, and CMD to locate
where to add the user creation, chown, and USER instructions so the runtime
container no longer runs as root.
In `@todo-worker/src/iii.ts`:
- Line 3: The environment fallback for engineWsUrl currently uses the nullish
coalescing operator (const engineWsUrl = process.env.III_URL ??
'ws://localhost:49134'), which leaves empty strings through and can break
registerWorker(); replace the nullish coalescing with a logical OR so an empty
III_URL falls back to 'ws://localhost:49134' (update the expression that defines
engineWsUrl in iii.ts accordingly), ensuring registerWorker() always receives a
valid URL.
---
Nitpick comments:
In `@image-resize/example/config.yaml`:
- Line 66: Replace the mutable image tag used in the image field
("docker.io/andersonofl/image-resize:latest") with an immutable reference:
either a specific versioned tag (e.g., v1.2.3) or an image digest (sha256:...),
so update the image value to "docker.io/andersonofl/image-resize:<version>" or
"docker.io/andersonofl/image-resize@sha256:<digest>" to ensure reproducible
deployments and safe rollbacks.
In `@registry/index.json`:
- Around line 28-37: The image entry in registry/index.json is not pinned to a
manifest digest; update the "image" field to use tag+digest format using the
"version" value (e.g., docker.io/andersonofl/todo-worker:0.1.0@sha256:<hash>) so
the exact artifact is referenced; obtain the correct sha256 digest from the
registry (docker pull or registry API) and replace the current "image" string,
and then either remove or update the "latest" field to reflect the pinned digest
to avoid relying on the mutable tag — change the "image" key and adjust "latest"
and/or "version" entries accordingly for the todo-worker entry.
In `@todo-worker/iii.worker.yaml`:
- Line 17: Replace the non-deterministic install command in the worker setup by
updating the install key in iii.worker.yaml (the "install" field) from "npm
install" to "npm ci" so CI and local builds use the lockfile for deterministic
installs; ensure the repo contains a lockfile (package-lock.json or
npm-shrinkwrap.json) before changing the "install" value.
In `@todo-worker/src/handlers.ts`:
- Around line 8-27: In createTodo, trim the incoming title before validation and
use the trimmed value for creation and logging: replace checks against raw title
with a trimmedTitle = title?.trim(), return the 400 error if trimmedTitle is
falsy, call store.create(trimmedTitle) and log the trimmedTitle (refer to
createTodo, logger.info, and store.create) so purely-whitespace titles are
rejected.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2ac22034-8bc1-4550-b143-2d540892196b
⛔ Files ignored due to path filters (1)
todo-worker/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (15)
.gitignoreimage-resize/example/config.yamlimage-resize/example/iii.workers.yamlregistry/index.jsontodo-worker/Dockerfiletodo-worker/Makefiletodo-worker/config.yamltodo-worker/iii.worker.yamltodo-worker/package.jsontodo-worker/src/handlers.tstodo-worker/src/hooks.tstodo-worker/src/iii.tstodo-worker/src/index.tstodo-worker/src/store.tstodo-worker/tsconfig.json
💤 Files with no reviewable changes (1)
- image-resize/example/iii.workers.yaml
| config: | ||
| III_ENGINE_URL: ws://localhost:49134 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify how worker config is mapped (env vs args vs config payload) and whether III_ENGINE_URL is consumed.
# Expected: find schema/loader code showing supported fields for worker entries and no consumer of III_ENGINE_URL.
rg -n -C3 'III_ENGINE_URL|III_URL|--url|workers:'
rg -n -C3 'struct .*Worker|type .*Worker|interface .*Worker|serde.*workers|yaml.*workers|image:'
rg -n -C3 'env:|args:|config:' --iglob '*.yaml' --iglob '*.yml'Repository: iii-hq/workers
Length of output: 19067
🏁 Script executed:
#!/bin/bash
# Search for config field processing and III_ENGINE_URL consumption
rg -n 'III_ENGINE_URL' --type rust --type ts --type js
rg -n 'config.*field\|parse.*config\|worker.*config' image-resize/src/ -A3
# Check main.rs for argument/env handling
head -50 image-resize/src/main.rsRepository: iii-hq/workers
Length of output: 1473
🏁 Script executed:
#!/bin/bash
# Search for how worker config field is processed when launching workers
rg -n 'config\[' --type rust --type ts --type js
rg -n 'worker.*config\|docker.*config\|container.*config' -B2 -A2
rg -n 'III_ENGINE_URL' -r . --type rust --type ts --type yaml
# Look for environment variable or arg passing in worker initialization
fd -e rs -e ts -e js | xargs rg -l 'env\[|process\.env\|std::env' | head -5Repository: iii-hq/workers
Length of output: 123
🏁 Script executed:
#!/bin/bash
# Look for orchestrator/controller code that processes worker definitions
fd -type f -name '*.ts' -o -name '*.js' -o -name '*.rs' | xargs rg -l 'worker.*config|docker.*spawn|container.*launch' | head -10
# Search the entire repo for how `config:` field in yaml is parsed
rg -n 'config:' image-resize/example/config.yaml -B2 -A2
# Check if there's any code handling environment or args from config field
rg -n 'config\s*:\s*\{|config\s*:\s*\[' --type rust --type ts --type yamlRepository: iii-hq/workers
Length of output: 1520
🏁 Script executed:
#!/bin/bash
# Search for other Docker-based workers (with 'image:' field) to see their config patterns
rg -n 'image:.*docker' -B5 -A5
# Look for any documentation or code about worker launching
fd -type f \( -name 'README*' -o -name '*.md' \) | xargs rg -l 'docker.*worker|image.*worker' 2>/dev/null
# Check if there's any reference to how config field is used for any worker type
rg -n 'config.*env|env.*config|worker.*spawn' -iRepository: iii-hq/workers
Length of output: 1148
🏁 Script executed:
#!/bin/bash
# Check README for guidance on worker config
cat README.md | grep -A20 -B5 -i 'config\|docker\|worker'
# Look for iii.worker.yaml files to understand worker manifest format
fd -name 'iii.worker.yaml' -exec cat {} \;
# Search for any orchestrator/engine code that might process worker definitions
rg -n 'workers\|worker.*config' README.mdRepository: iii-hq/workers
Length of output: 1839
🏁 Script executed:
#!/bin/bash
# Look at worker manifest files to understand proper worker definition
find . -name 'iii.worker.yaml' -exec echo "=== {} ===" \; -exec cat {} \;Repository: iii-hq/workers
Length of output: 603
🏁 Script executed:
#!/bin/bash
# Search for env: or args: usage in config.yaml files
rg -n 'env:|args:' image-resize/example/config.yaml todo-worker/config.yaml -B2 -A2
# Check if any worker entries in config.yaml use env: or args:
rg -n '^\s*(env:|args:|image:|config:|name:)' image-resize/example/config.yaml | head -30Repository: iii-hq/workers
Length of output: 209
🏁 Script executed:
#!/bin/bash
# Search for any engine or launcher code that processes worker definitions
# Look for files that might handle Docker container startup
rg -n 'image.*docker\|docker.*run\|container.*spawn\|worker.*launch' -i
# Check if there are any other example config files
find . -name 'config.yaml' -o -name 'example*.yaml'
# Look for documentation about worker configuration format
find . -name '*.md' -exec rg -l 'worker.*config\|config.*worker' {} \;Repository: iii-hq/workers
Length of output: 118
🏁 Script executed:
#!/bin/bash
# Final verification: confirm III_ENGINE_URL is truly not used by checking all references
rg -n 'III_ENGINE_URL' --type-add 'all:include,rs,ts,js,yaml,dockerfile' -t all
# Check if there's any schema or documentation about supported config fields
find . -name 'schema*' -o -name '*schema*'Repository: iii-hq/workers
Length of output: 117
🏁 Script executed:
#!/bin/bash
# Simple final check: find all occurrences of III_ENGINE_URL
grep -r "III_ENGINE_URL" .Repository: iii-hq/workers
Length of output: 216
Remove config: III_ENGINE_URL — the binary expects --url CLI argument or III_URL env var, not a config field.
The image-resize binary (main.rs, line 21) accepts engine URL via --url flag only; there is no code path consuming III_ENGINE_URL from a config payload. The Dockerfile correctly passes --url in CMD (line 51), but the config.yaml entry (lines 67–68) has no effect.
Compare with the working Dockerfile pattern and the binary's actual argument parsing:
- Binary accepts:
--url ws://host:port(CLI flag) - Dockerfile uses:
CMD ["--url", "ws://host.containers.internal:49134"]✓ - config.yaml uses:
config: III_ENGINE_URL: ws://localhost:49134✗ (ignored)
Replace the config entry with the mechanism your orchestrator uses to pass environment variables or CLI arguments to the container (e.g., env: or args: if supported for Docker workers in your engine configuration schema).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@image-resize/example/config.yaml` around lines 67 - 68, The config.yaml entry
"config: III_ENGINE_URL" is ineffective because the image-resize binary
(main.rs) only accepts the engine URL via the CLI flag "--url" or the
environment variable "III_URL"; remove the "config: III_ENGINE_URL" field and
instead supply the URL using your orchestrator's mechanism — e.g., add an env
var "III_URL" with the ws:// value or pass the CLI arg "--url ws://..."
(matching the Dockerfile CMD pattern) so the image-resize binary actually
receives the engine URL.
| FROM node:22-slim | ||
| WORKDIR /app | ||
| COPY package.json package-lock.json ./ | ||
| RUN npm ci --omit=dev | ||
| COPY --from=build /app/dist ./dist | ||
| ENV III_URL=ws://localhost:49134 | ||
| CMD ["node", "/app/dist/index.js"] |
There was a problem hiding this comment.
Run the runtime container as a non-root user.
Line 11–17 currently execute as root, which weakens container isolation and violates baseline hardening guidance.
Suggested hardening patch
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
+RUN chown -R node:node /app
+USER node
ENV III_URL=ws://localhost:49134
CMD ["node", "/app/dist/index.js"]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@todo-worker/Dockerfile` around lines 11 - 17, The Dockerfile is running the
container as root; create and use a non-root user by adding a dedicated
unprivileged user and group, chown the WORKDIR (/app) and any copied files
(e.g., dist) to that user, and add a USER <username> line before CMD to switch
to that user; reference the Dockerfile directives WORKDIR, COPY --from=build
./dist, RUN npm ci, and CMD to locate where to add the user creation, chown, and
USER instructions so the runtime container no longer runs as root.
| @@ -0,0 +1,12 @@ | |||
| import { registerWorker } from 'iii-sdk' | |||
|
|
|||
| const engineWsUrl = process.env.III_URL ?? 'ws://localhost:49134' | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify where III_URL is injected and how fallback is computed.
rg -nP 'III_URL|engineWsUrl|\?\?|\|\|' todo-worker/src/iii.ts todo-worker/Makefile todo-worker/Dockerfile todo-worker/iii.worker.yaml -C2Repository: iii-hq/workers
Length of output: 1162
Replace nullish coalescing with logical OR to handle empty string values.
Line 3 uses ??, which treats empty strings as valid and bypasses the fallback. Since the Makefile passes III_URL without a value, an empty string could reach registerWorker() and break registration.
Suggested fix
-const engineWsUrl = process.env.III_URL ?? 'ws://localhost:49134'
+const engineWsUrl = process.env.III_URL?.trim() || 'ws://localhost:49134'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const engineWsUrl = process.env.III_URL ?? 'ws://localhost:49134' | |
| const engineWsUrl = process.env.III_URL?.trim() || 'ws://localhost:49134' |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@todo-worker/src/iii.ts` at line 3, The environment fallback for engineWsUrl
currently uses the nullish coalescing operator (const engineWsUrl =
process.env.III_URL ?? 'ws://localhost:49134'), which leaves empty strings
through and can break registerWorker(); replace the nullish coalescing with a
logical OR so an empty III_URL falls back to 'ws://localhost:49134' (update the
expression that defines engineWsUrl in iii.ts accordingly), ensuring
registerWorker() always receives a valid URL.
Summary
Adds a todo-worker — a Node.js/TypeScript quickstart template that demonstrates how to build an iii worker with the
iii-sdk. It implements a full CRUD API for todos backed by an in-memory store, making it a cloneable starting point for new workers.What's included
POST /todos,GET /todos,GET /todos/:id,PUT /todos/:id,DELETE /todos/:idstore.ts(data) →handlers.ts(business logic) →hooks.ts(SDK wiring) →index.ts(route registration)useApihelper hook that registers both the function and HTTP trigger in a single calltsc, run withnode)build,run,dev, andcleantargetsiii.worker.yamlmanifest andconfig.yamlwith HTTP + observability worker configurationregistry/index.json) — added as an OCI-type worker atdocker.io/andersonofl/todo-workerOther changes
node_modulesto root.gitignoreimage-resizeexample worker config from the deletediii.workers.yamlintoconfig.yaml(workers section)Type of Change
Test Plan
npm install && npm run devagainst a local iii enginemake buildand run withmake runSummary by CodeRabbit
New Features
todo-workerwith a complete REST API for managing todos, supporting create, list, retrieve, update, and delete operations.Chores
.gitignoreto excludenode_modules.