Putnami
DocsGitHub

Licensed under FSL-1.1-MIT

Getting Started
Concepts
How To
Build A Web App
Build An Api Service
Share Code Between Projects
Configure Your App
Add Persistence
Add Authentication
Add Background Jobs
Develop With Ai
Structure Business Logic With Di
Upgrade Putnami
Principles
Tooling & Workspace
Workspace
Cli
Jobs & Caching
Extensions
Templates
Error Handling
Frameworks
Typescript
ExtensionOverviewWebReact RoutingForms And ActionsStatic FilesApiErrors And ResponsesConfigurationLoggingHttp And MiddlewareDependency InjectionPlugins And LifecycleSessionsAuthPersistenceEventsStorageCachingWebsocketsTestingHealth ChecksTelemetryProto GrpcSmart ClientSchema
Go
ExtensionOverviewHttpDependency InjectionPlugins And LifecycleConfigurationSecurityPersistenceErrorsEventsStorageCachingLoggingTelemetryGrpcService ClientsValidationOpenapiTesting
Python
Extension
Platform
Ci
  1. DocsSeparator
  2. Tooling & WorkspaceSeparator
  3. Jobs & Caching

Jobs & Caching

Extensions expose functionality through commands (the user-facing API) and implement it with tasks (atomic executable units). The CLI orchestrates tasks across projects and uses caching to avoid redundant work.

Commands and tasks

A command is a user-facing operation (e.g., putnami build). Each command defines a pipeline of steps that reference tasks:

{
  "commands": {
    "build": {
      "run": [
        { "id": "generate", "task": "build-generate", "dependsOn": ["^generate"] },
        { "id": "transpile", "task": "build-transpile", "dependsOn": ["generate"] }
      ]
    }
  },
  "tasks": {
    "build-generate": {
      "kind": "command",
      "command": "{extensionRoot}/bin/build",
      "args": ["generate"],
      "cwd": "{projectRoot}",
      "timeoutMs": 300000,
      "inputs": {
        "source": { "from": "project", "files": ["src/**/*.ts"] },
        "config": { "from": "project", "files": ["package.json"] }
      },
      "cache": { "enabled": true, "deterministic": true }
    }
  }
}

Command fields:

Field Required Description
run yes Pipeline step DAG
description no Human-readable description
activationFiles no File patterns that must exist for the command to activate
flags no CLI flags the command accepts
priority no Higher priority wins when multiple extensions provide the same command
quiet no Suppress from job recap
channel no Execution channel for concurrency control

Task fields:

Field Required Description
kind yes Execution kind (currently only command)
command yes Command to execute
args no Command arguments
cwd no Working directory (supports template variables)
env no Environment variables
timeoutMs no Execution timeout (default: 300000ms)
inputs no Input port declarations for cache invalidation
outputs no Output port declarations
cache no Cache policy ({ "enabled": true })

Template variables for command, args, cwd, and env:

Variable Description
{workspaceRoot} Absolute path to workspace root
{projectRoot} Absolute path to project root
{extensionRoot} Absolute path to extension package

Pipeline steps

Each step in a command's run array references a task and declares its position in the DAG:

Step fields

Field Required Description
id yes Unique step identifier
task yes Name of a task in the extension's tasks section
dependsOn no Step IDs or external references this step depends on
with no Input bindings passed to the task
if no Plan-time condition (excludes step from DAG when false)
when no Runtime condition (skips step when false)
optional no If true, failure marks step as skipped instead of failed

Dependency references

The dependsOn field supports several reference types:

Pattern Meaning
"transpile" Wait for step transpile in the same command
"^generate" Wait for the upstream dependency's generate step
"*job" Wait for all downstream dependents
"/project:build" Wait for another project's build command
"@ext/name:job" Extension-scoped reference

The ^ prefix is the most common — it creates cross-project dependency chains so downstream projects wait for their dependencies to complete the same phase.

Embedded flags

Append flags after a dependency reference to request a specific subset of work:

"dependsOn": ["^build --generate"]

The planner splits each string on whitespace — the first token is the reference, the rest are forwarded as arguments. When multiple steps schedule the same dependency with different flags, the planner upgrades to an unrestricted execution so the result satisfies all dependents.

Task-level upstream dependencies

When a ^ ref matches a step in the current pipeline, the planner emits only that individual task for each upstream project — not the full pipeline:

{
  "commands": {
    "package": {
      "run": [
        { "id": "generate", "task": "build-generate", "dependsOn": ["^generate"] },
        { "id": "transpile", "task": "build-transpile", "dependsOn": ["generate", "^transpile"] },
        { "id": "docker", "task": "package-docker", "dependsOn": ["transpile"], "if": "params.docker" }
      ]
    }
  }
}

Running putnami package my-app (where my-app depends on sdk which depends on utils):

  • utils and sdk get only generate and transpile tasks
  • my-app gets the full pipeline (generate, transpile, docker)
  • No upstream docker tasks are scheduled

When the ^ ref does NOT match any step in the pipeline, the planner schedules the full command on upstream projects.

Input bindings

Steps can pass data to tasks via with:

{
  "id": "transpile",
  "task": "build-transpile",
  "with": {
    "target": { "value": "production" },
    "config": { "from": "command", "path": "flags.target" },
    "schemas": { "fromStep": "generate", "output": "schemas" }
  }
}

Conditional execution

  • if — evaluated at plan-time. Removes the step entirely from the DAG: "if": "params.compile"
  • when — evaluated at runtime. Skips the step but keeps it in the DAG: "when": "steps.test.status == 'success'"

Orchestration

Job orchestration happens in two phases:

Planning:

  1. Resolve target projects (--all, --impacted, explicit names, etc.)
  2. Load extension command and task definitions
  3. Expand dependsOn into concrete task nodes (project + task pairs)
  4. Build a dependency DAG

Execution:

  1. Start all tasks concurrently
  2. Each task waits on its dependencies before running
  3. Results and events are collected into a single session

Parallelism is automatic — tasks without mutual dependencies run at the same time. Use --max-parallel <n> to cap concurrency.

If a dependency is already in the scheduling stack, the edge is skipped to avoid cycles. Planning continues, but the cycle indicates a configuration problem.

Use --plan to preview the execution tree without running:

putnami build --impacted --plan

Caching

Caching avoids re-executing tasks whose inputs have not changed.

How it works:

  1. Tasks declare inputs with file globs (from: "project", files: [...])
  2. The runner computes a hash from matched files
  3. Cache keys combine:
    • Task identity (extension, task name, project)
    • Task parameters
    • Dependent task hashes
    • Project file hash

If any file matched by the input declaration changes, the hash changes and the cache is invalidated.

Flags:

  • --no-cache — Skip cache reads but still write results
  • putnami cache clean — Delete all cached data

Watch mode

The --watch flag enables pipeline-aware file watching. When files change, running tasks are aborted and the pipeline is re-executed. Watch mode forces --no-cache to ensure fresh results on every iteration.

putnami test . --watch
putnami build --all --watch
putnami serve my-app --watch

Behavior:

  • Polling-based file system monitoring (100ms intervals)
  • 150ms debounce for rapid edits
  • Automatically excluded: .git/, node_modules/, .putnami/, dist/, build/, __pycache__/, .venv/
  • Changed files are mapped to projects, and only affected tasks re-run
  • Dependencies propagate transitively — if a library changes, dependent apps re-run

Multi-service sessions

When running putnami serve, watch mode discovers and starts all runtime service dependencies alongside the primary target. Services are found via putnami.runsWith in package.json or by walking the dependency graph for projects with a ./serve export.

putnami serve web-app              # Starts web-app + discovered services
putnami serve web-app --no-services  # Only the target project

On file change, only affected services are selectively restarted. Unaffected services keep running.

Disabling jobs

Jobs can be disabled at workspace or project level. Disabled jobs are excluded during planning.

Workspace level — in the root package.json:

{
  "putnami": {
    "disable": {
      "jobs": ["lint"]
    }
  }
}

Project level — in the project's package.json:

{
  "putnami": {
    "disable": {
      "jobs": ["@putnami/typescript:lint"]
    }
  }
}

Unqualified names (e.g., "lint") disable all jobs with that name. Qualified names (e.g., "@putnami/typescript:lint") disable only that extension's job.

Overriding extension jobs

A project can override extension-provided jobs in its putnami.json under the jobs key:

{
  "name": "my-custom-app",
  "extensions": ["@putnami/typescript"],
  "jobs": {
    "build": {
      "kind": "command",
      "command": "bun",
      "args": ["run", "{projectRoot}/scripts/build.ts"],
      "cwd": "{projectRoot}",
      "dependsOn": ["^build"]
    }
  }
}

Overrides only affect the specific project. The project acts as its own job provider — {extensionRoot} resolves to {projectRoot}. You can also define entirely new job names that don't exist in any extension.

Job status

Jobs return one of: OK, FAILED, or SKIP (and may be shown as cached when served from cache).

Common skip cases:

  • A dependency failed
  • --skip-ci is set and CI=true
  • The job decides to no-op in its own implementation
  • The job is listed in disable.jobs

Command naming scheme

Putnami uses a consistent naming scheme across all extensions:

Command Scope Description
build project Compile and bundle
test project Run tests
lint project Format and lint code
serve project Run development server
package project Create distribution packages
publish project Publish to registries and asset storage
release workspace Changelog, git tag, version bump

On this page

  • Jobs & Caching
  • Commands and tasks
  • Pipeline steps
  • Step fields
  • Dependency references
  • Embedded flags
  • Task-level upstream dependencies
  • Input bindings
  • Conditional execution
  • Orchestration
  • Caching
  • Watch mode
  • Multi-service sessions
  • Disabling jobs
  • Overriding extension jobs
  • Job status
  • Command naming scheme