All skills

Waterline ORM query language, model definitions, associations, and data access patterns for Sails.js. Use this skill when writing, reviewing, or debugging Waterline queries, model attributes, associations, lifecycle callbacks, validations, or any database interaction in a Sails.js application.

Use this Skill: https://skilld.dev/gh/sailscastshq/boring-stack/waterline

This session only. Nothing lands on disk.

rulesmodel-methods.md

≈2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Model Methods

All model methods are asynchronous. They return a chainable Deferred object that resolves when .exec(), await, or .then() is called.

find()

Find records matching criteria. Returns an array (empty [] if no matches).

// Basic find
var users = await User.find({ emailStatus: 'verified' })

// With projection
var users = await User.find({
  where: { emailStatus: 'verified' },
  select: ['name', 'email']
})

// With sort, limit, skip, populate
var invoices = await Invoice.find({ creator: userId, status: 'paid' })
  .select(['publicId', 'invoiceNumber', 'totalAmount', 'currency', 'status'])
  .populate('client')
  .sort('updatedAt DESC')
  .paginate(page, perPage)

Chainable: .where(), .sort(), .limit(), .skip(), .select(), .omit(), .populate(), .meta(), .decrypt(), .intercept(), .tolerate()

findOne()

Find a single record. Returns the record dictionary or undefined (not an error) if no match.

var user = await User.findOne({ email: email.toLowerCase() })
if (!user) {
  throw { badCombo: { problems: [{ login: 'Wrong email/password.' }] } }
}

// By ID (shorthand)
var user = await User.findOne(this.req.session.userId)

// With populate
var invoice = await Invoice.findOne({ publicId: id, creator: userId })
  .populate('items')
  .populate('creator')
  .populate('client')

// With select
var user = await User.findOne({ id: userId }).select(['password', 'email'])

// Populate with subcriteria
var ticket = await Ticket.findOne({ id: ticketId })
  .populate('ticketType', { select: ['name'] })
  .populate('order')

Does NOT support skip or limit.

findOrCreate()

Find an existing record or create a new one. The wasCreated boolean is only available in callback form:

// Callback form (provides wasCreated)
User.findOrCreate(
  { or: [{ googleId: googleUser.id }, { email: googleUser.email }] },
  {
    googleId: googleUser.id,
    email: googleUser.email,
    fullName: googleUser.name,
    emailStatus: googleUser.verified_email ? 'verified' : 'unverified'
  }
).exec(async (error, user, wasCreated) => {
  if (error) throw error
  if (!wasCreated) {
    await User.updateOne({ id: user.id }).set({ emailStatus: 'verified' })
  }
})

// Await form (no wasCreated)
var item = await InvoiceItem.findOrCreate(criteria, {
  ...item,
  invoice: invoiceToUpdate.id
})

create()

Create a new record. Returns nothing by default -- chain .fetch() to get the created record.

// Fire and forget (no return value)
await HistoricalUsageSnapshot.create(Object.assign({}, inputs))

// With .fetch() to get the created record back
var newUser = await User.create({
  email,
  password,
  fullName,
  tosAcceptedByIp: this.req.ip,
  emailProofToken,
  emailProofTokenExpiresAt: Date.now() + sails.config.custom.emailProofTokenTTL
}).fetch()

// With .intercept() for error handling
var newUser = await User.create({
  firstName,
  lastName,
  emailAddress: newEmailAddress,
  password: await sails.helpers.passwords.hashPassword(password)
})
  .intercept('E_UNIQUE', 'emailAlreadyInUse')
  .intercept({ name: 'UsageError' }, 'invalid')
  .fetch()

// With .tolerate() for idempotent operations
await NewsletterSubscription.create({ emailAddress }).tolerate('E_UNIQUE')

Input dictionaries are mutated in-place (Sails v1.0+). Clone if you need to preserve the original.

createEach()

Create multiple records at once:

await User.createEach([
  { name: 'Finn', email: 'finn@example.com' },
  { name: 'Jake', email: 'jake@example.com' }
])

// With .fetch()
var users = await User.createEach(userDataArray).fetch()

Watch for database size limits (MySQL: 4MB default query size, MongoDB: 16MB per document).

update()

Update all records matching criteria. Returns nothing by default.

// Bulk update
await User.update({ emailStatus: 'pending' }).set({ emailStatus: 'verified' })

// With .fetch() to get updated records
var updatedUsers = await User.update({ emailStatus: 'pending' })
  .set({ emailStatus: 'verified' })
  .fetch()

.set() is required. Does NOT support skip, limit, or select.

updateOne()

Update a single record. Returns the updated record or undefined. Throws if criteria matches more than one record.

var updatedUser = await User.updateOne({ id: user.id }).set({
  password,
  passwordResetToken: '',
  passwordResetTokenExpiresAt: 0
})

if (updatedUser) {
  sails.log('Successfully updated.')
} else {
  sails.log('No matching user found.')
}

Does NOT support .fetch() -- it inherently returns the record.

Background update (fire-and-forget with .exec()):

User.updateOne({ id: loggedInUser.id })
  .set({ lastSeenAt: Date.now() })
  .exec((err) => {
    if (err) {
      sails.log.error('Background task failed:', err)
    }
  })

destroy()

Destroy all records matching criteria. An empty {} destroys ALL records.

// By criteria
await User.destroy({
  emailStatus: 'unconfirmed',
  createdAt: { '<': new Date('2023-01-01') }
})

// By multiple IDs
await User.destroy({ id: { in: [3, 97] } })

// With .fetch() to get deleted records
var deleted = await User.destroy({ inactive: true }).fetch()

Does NOT support skip, limit, or select.

destroyOne()

Destroy a single record. Returns the destroyed record or undefined. Throws if criteria matches more than one record.

var deleted = await User.destroyOne({ id: 4 })
if (deleted) {
  sails.log('Deleted user:', deleted.id)
} else {
  sails.log('No user found with id: 4')
}

// Used in cleanup
await User.destroyOne({ id: genesisUser.id })
await Team.destroyOne({ id: defaultTeam.id })

Does NOT support .fetch() -- it inherently returns the record.

validate()

Synchronous method that validates a value against a model attribute's rules without touching the database:

try {
  var normalized = User.validate('emailAddress', inputValue)
} catch (err) {
  if (err.code === 'E_VALIDATION') {
    err.all.forEach((woe) => {
      sails.log(woe.attrName + ': ' + woe.message)
    })
  }
}

This is the only synchronous model method. Useful for pre-validating data before third-party API calls.

Method Behavior Summary

Method Returns by default With .fetch() Safety check
find Array N/A --
findOne Dict or undefined N/A --
findOrCreate Dict N/A Throws if criteria matches multiple
create Nothing Created record --
createEach Nothing Created records array --
update Nothing Updated records array --
updateOne Updated record or undefined N/A (inherent) Throws if >1 match
destroy Nothing Destroyed records array --
destroyOne Destroyed record or undefined N/A (inherent) Throws if >1 match
validate Normalized value (sync) N/A Throws E_VALIDATION

Source: SKILL.md on GitHub

1 warning17d4 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    The skill provides comprehensive documentation and best practice rules for writing Waterline ORM queries and model definitions in Sails.js applications. It contains only Markdown files and does not include any executable scripts or malicious instructions.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • Runlayer7mo

    10/13 files flagged

Signed by skilld at 383ecf8. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 days ago.

Activeupdated 8 months ago
Other metadata
metadata
{
  "author": "sailscastshq",
  "version": "1.0.0",
  "tags": "waterline, sails, orm, database, query, models"
}

README badge

README badge for sailscastshq/boring-stack/waterline