Skip to content

Job Inputs ​

Quest jobs can accept inputs, allowing you to create flexible, reusable jobs that adapt to different scenarios.

Defining Inputs ​

Define inputs in your job using the standard Sails script format:

javascript
// scripts/cleanup-data.js
module.exports = {
  friendlyName: 'Cleanup old data',

  quest: {
    interval: '1 day'
  },

  inputs: {
    daysOld: {
      type: 'number',
      required: true,
      description: 'Delete records older than this many days'
    },

    dryRun: {
      type: 'boolean',
      defaultsTo: false,
      description: 'Preview what would be deleted without actually deleting'
    },

    model: {
      type: 'string',
      isIn: ['orders', 'sessions', 'logs'],
      defaultsTo: 'logs',
      description: 'Which type of data to clean up'
    }
  },

  fn: async function (inputs) {
    const cutoffDate = new Date()
    cutoffDate.setDate(cutoffDate.getDate() - inputs.daysOld)

    const Model = sails.models[inputs.model]

    if (inputs.dryRun) {
      const count = await Model.count({
        createdAt: { '<': cutoffDate }
      })
      sails.log.info(`Would delete ${count} ${inputs.model}`)
      return { wouldDelete: count, dryRun: true }
    }

    const deleted = await Model.destroy({
      createdAt: { '<': cutoffDate }
    }).fetch()

    return { deleted: deleted.length, model: inputs.model }
  }
}

Input Types ​

Quest supports all Sails input types:

javascript
inputs: {
  // String input
  email: {
    type: 'string',
    isEmail: true,
    required: true
  },

  // Number input
  limit: {
    type: 'number',
    min: 1,
    max: 100,
    defaultsTo: 10
  },

  // Boolean input
  verbose: {
    type: 'boolean',
    defaultsTo: false
  },

  // JSON input
  config: {
    type: 'json',
    defaultsTo: {}
  },

  // Ref (any type)
  data: {
    type: 'ref',
    description: 'Arbitrary data'
  }
}

Default Values ​

Inputs can have default values that are used when not specified:

javascript
// scripts/send-notifications.js
module.exports = {
  friendlyName: 'Send notifications',

  quest: {
    interval: '1 hour'
  },

  inputs: {
    channel: {
      type: 'string',
      isIn: ['email', 'sms', 'push'],
      defaultsTo: 'email'
    },

    priority: {
      type: 'string',
      isIn: ['high', 'normal', 'low'],
      defaultsTo: 'normal'
    },

    batchSize: {
      type: 'number',
      defaultsTo: 50
    }
  },

  fn: async function (inputs) {
    const notifications = await Notification.find({
      status: 'pending',
      channel: inputs.channel,
      priority: inputs.priority
    }).limit(inputs.batchSize)

    // Send notifications...

    return { sent: notifications.length }
  }
}

Configuring Inputs in Config ​

You can set input values in your Quest configuration:

javascript
// config/quest.js
module.exports.quest = {
  jobs: [
    {
      name: 'cleanup-data',
      interval: '1 day',
      inputs: {
        daysOld: 90,
        model: 'logs',
        dryRun: false
      }
    },
    {
      name: 'cleanup-data',
      interval: '1 week',
      inputs: {
        daysOld: 30,
        model: 'sessions',
        dryRun: false
      }
    }
  ]
}

Input Priority ​

Quest merges inputs from four sources, with later sources taking priority over earlier ones:

  1. Config inputs (lowest) - inputs in config/quest.js
  2. Script quest inputs - inputs in script's quest property
  3. Script input defaults - defaultsTo values from script's inputs definition
  4. Runtime inputs (highest) - Passed to sails.quest.run()
javascript
// config/quest.js
module.exports.quest = {
  jobs: [
    {
      name: 'cleanup-data',
      interval: '1 day',
      inputs: {
        daysOld: 90, // 1. Config inputs (base)
        model: 'logs',
        verbose: false,
        format: 'json'
      }
    }
  ]
}
javascript
// scripts/cleanup-data.js
module.exports = {
  quest: {
    interval: '5 minutes', // Overrides config scheduling
    inputs: {
      model: 'sessions' // 2. Script quest inputs (overrides config)
    }
  },

  inputs: {
    daysOld: {
      type: 'number',
      defaultsTo: 30 // 3. Script defaults (overrides quest inputs)
    },
    verbose: {
      type: 'boolean',
      defaultsTo: true // 3. Script defaults (overrides config)
    },
    format: {
      type: 'string',
      defaultsTo: 'csv' // 3. Script defaults (overrides config)
    }
  },

  fn: async function (inputs) {
    // ...
  }
}
javascript
// Running the job
await sails.quest.run('cleanup-data', {
  daysOld: 7 // 4. Runtime inputs (highest priority)
})

// Final merged inputs:
// {
//   daysOld: 7,          <- runtime (overrides script default 30)
//   model: 'sessions',   <- script quest.inputs (overrides config 'logs')
//   verbose: true,       <- script defaults (overrides config false)
//   format: 'csv'        <- script defaults (overrides config 'json')
// }

This priority system allows you to:

  • Set environment-specific base values in config/quest.js
  • Override config values in script's quest.inputs for job-specific settings
  • Define sensible defaults in script's inputs definition
  • Pass custom values at runtime for one-off executions

Running Jobs with Custom Inputs ​

When running jobs manually, you can override inputs:

javascript
// Run with custom inputs
await sails.quest.run('cleanup-data', {
  daysOld: 7,
  model: 'orders',
  dryRun: true
})

// Run with default inputs
await sails.quest.run('cleanup-data')

Environment-Based Inputs ​

Use environment variables for sensitive inputs:

javascript
// scripts/backup-to-s3.js
module.exports = {
  friendlyName: 'Backup to S3',

  quest: {
    cron: '0 3 * * *' // Daily at 3 AM
  },

  inputs: {
    bucket: {
      type: 'string',
      defaultsTo: process.env.S3_BACKUP_BUCKET || 'my-backups'
    },

    region: {
      type: 'string',
      defaultsTo: process.env.AWS_REGION || 'us-east-1'
    },

    encrypt: {
      type: 'boolean',
      defaultsTo: process.env.NODE_ENV === 'production'
    }
  },

  fn: async function (inputs) {
    // Use AWS SDK with inputs
    const s3 = new AWS.S3({
      region: inputs.region
    })

    // Perform backup...

    return { bucket: inputs.bucket, encrypted: inputs.encrypt }
  }
}

Validation ​

Inputs are automatically validated before the job runs:

javascript
// scripts/process-payments.js
module.exports = {
  friendlyName: 'Process payments',

  quest: {
    interval: '10 minutes'
  },

  inputs: {
    minAmount: {
      type: 'number',
      min: 0,
      required: true
    },

    maxAmount: {
      type: 'number',
      custom: function (value) {
        // Custom validation
        if (value <= this.minAmount) {
          return 'maxAmount must be greater than minAmount'
        }
        return true
      }
    },

    currency: {
      type: 'string',
      isIn: ['USD', 'EUR', 'GBP'],
      required: true
    }
  },

  fn: async function (inputs) {
    const payments = await Payment.find({
      amount: {
        '>=': inputs.minAmount,
        '<=': inputs.maxAmount
      },
      currency: inputs.currency,
      status: 'pending'
    })

    // Process payments...

    return { processed: payments.length }
  }
}

Dynamic Inputs Example ​

Create flexible jobs that adapt based on inputs:

javascript
// scripts/flexible-reporter.js
module.exports = {
  friendlyName: 'Flexible reporter',

  quest: {
    interval: '1 hour'
  },

  inputs: {
    reportType: {
      type: 'string',
      isIn: ['sales', 'users', 'inventory'],
      required: true
    },

    timeframe: {
      type: 'string',
      isIn: ['hour', 'day', 'week', 'month'],
      defaultsTo: 'day'
    },

    format: {
      type: 'string',
      isIn: ['json', 'csv', 'pdf'],
      defaultsTo: 'json'
    },

    recipients: {
      type: 'json',
      defaultsTo: [],
      description: 'Email addresses to send report to'
    }
  },

  fn: async function (inputs) {
    // Generate report based on type
    let data
    switch (inputs.reportType) {
      case 'sales':
        data = await generateSalesReport(inputs.timeframe)
        break
      case 'users':
        data = await generateUserReport(inputs.timeframe)
        break
      case 'inventory':
        data = await generateInventoryReport(inputs.timeframe)
        break
    }

    // Format report
    let report
    switch (inputs.format) {
      case 'csv':
        report = await formatAsCSV(data)
        break
      case 'pdf':
        report = await formatAsPDF(data)
        break
      default:
        report = data
    }

    // Send to recipients if specified
    if (inputs.recipients.length > 0) {
      for (const email of inputs.recipients) {
        await sails.helpers.sendEmail.with({
          to: email,
          subject: `${inputs.reportType} Report`,
          attachments: [report]
        })
      }
    }

    return {
      type: inputs.reportType,
      format: inputs.format,
      sentTo: inputs.recipients.length
    }
  }
}

Best Practices ​

  1. Use descriptive names - Make input purposes clear
  2. Provide defaults - Make jobs work out-of-the-box
  3. Validate thoroughly - Use type checking and custom validation
  4. Document inputs - Add descriptions for clarity
  5. Use environment variables - For sensitive configuration
  6. Keep inputs simple - Complex logic belongs in the fn, not inputs

All open source projects are released under the MIT License.