@rvoh/dream
    Preparing search index...

    Class Decorators<TD, T>

    Type Parameters

    Index

    Constructors

    Methods

    • AfterCreate decorator

      class User {
      @deco.AfterCreate()
      public doSomething() {
      console.log('hi!')
      }
      }

      Parameters

      Returns any

      The AfterCreate decorator

    • AfterCreateCommit decorator

      class User {
      @deco.AfterCreateCommit()
      public doSomething() {
      console.log('hi!')
      }
      }

      The decorated method is called with no argument, once the transaction has committed, so declaring a parameter on it is a compile error. A subclass override of the method that is not itself decorated is not checked; it is still called with no argument.

      Parameters

      Returns CommitHookDecorator

      The AfterCreateCommit decorator

    • AfterDestroyCommit decorator

      class User {
      @deco.AfterDestroyCommit()
      public doSomething() {
      console.log('hi!')
      }
      }

      The decorated method is called with no argument, once the transaction has committed, so declaring a parameter on it is a compile error. A subclass override of the method that is not itself decorated is not checked; it is still called with no argument.

      Parameters

      Returns CommitHookDecorator

      The AfterDestroyCommit decorator

    • AfterSave decorator

      class User {
      @deco.AfterSave()
      public doSomething() {
      console.log('hi!')
      }
      }

      Parameters

      Returns any

      The AfterSave decorator

    • AfterSaveCommit decorator

      class User {
      @deco.AfterSaveCommit()
      public doSomething() {
      console.log('hi!')
      }
      }

      The decorated method is called with no argument, once the transaction has committed, so declaring a parameter on it is a compile error. A subclass override of the method that is not itself decorated is not checked; it is still called with no argument.

      Parameters

      Returns CommitHookDecorator

      The AfterSaveCommit decorator

    • AfterUpdate decorator

      class User {
      @deco.AfterUpdate()
      public doSomething() {
      console.log('hi!')
      }
      }

      Parameters

      Returns any

      The AfterUpdate decorator

    • AfterUpdateCommit decorator

      class User {
      @deco.AfterUpdateCommit()
      public doSomething() {
      console.log('hi!')
      }
      }

      The decorated method is called with no argument, once the transaction has committed, so declaring a parameter on it is a compile error. A subclass override of the method that is not itself decorated is not checked; it is still called with no argument.

      Parameters

      Returns CommitHookDecorator

      The AfterUpdateCommit decorator

    • BeforeCreate decorator

      class User {
      @deco.BeforeCreate()
      public doSomething() {
      console.log('hi!')
      }
      }

      Parameters

      Returns any

      The BeforeCreate decorator

    • BeforeSave decorator

      class User {
      @deco.BeforeSave()
      public doSomething() {
      console.log('hi!')
      }
      }

      Parameters

      Returns any

      The BeforeSave decorator

    • BeforeUpdate decorator

      class User {
      @deco.BeforeUpdate()
      public doSomething() {
      console.log('hi!')
      }
      }

      Parameters

      Returns any

      The BeforeUpdate decorator

    • The Encrypted decorator automatically encrypts (upon setting) and decrypts (upon getting) so that the encrypted value is stored in the database.

      class User {
      @deco.Encrypted()
      // automatically sets `encryptedSsn` to the encrypted value that
      // `ssn` is set to in new/create/update, e.g., `await user.update({ ssn })`
      public ssn: string

      // automatically sets `myEncryptedPhone` to the encrypted value that
      // `phone` is set to new/create/update, e.g., `await user.update({ phone })`
      @deco.Encrypted('myEncryptedPhone')
      public phone: string

      // automatically sets `myEncryptedMfaSettings` to the encrypted value that
      // `mfaSettings` is set to in new/create/update, and declares the
      // value's OpenAPI shape, since it is not a string
      @deco.Encrypted({
      column: 'myEncryptedMfaSettings',
      openapi: { type: ['object', 'null'], properties: { token: 'string' } },
      })
      public mfaSettings: { token: string } | null
      }

      The decorated property may hold any JSON-serializable value. Its OpenAPI shape is what Psychic uses to cast the property in params and to render it in request body and serializer OpenAPI. Without an openapi option, that shape is 'string', or ['string', 'null'] when the backing column allows null. Pass openapi when the property holds a non-string value, such as an object, a number, or an array; it accepts any shape @deco.Virtual accepts. The declared shape is used exactly as given and replaces the nullability inferred from the backing column, so when the backing column allows null, include 'null' in the shape, e.g. type: ['object', 'null'].

      Parameters

      • this: Decorators<TD>
      • OptionalcolumnOrOptions:
            | keyof T["DB"][T["table"] & keyof T["DB"]] & string
            | EncryptedOptions<keyof T["DB"][T["table"] & keyof T["DB"]] & string>

        Either the backing column's name, or an options object:

        • column — the backing column's name; if omitted, then 'encrypted' is prepended to the Pascal cased version of the decorated field
        • openapi — the OpenAPI shape of the decorated property's value; if omitted, the property's shape is a string (see above)

      Returns any

      An Encrypted decorator

    • The Scope decorator decorates a static method that accepts and returns a Dream Query.

      class Collar {
      @deco.Scope({ default: true })
      public static hideHiddenCollars(query: Query<Collar>) {
      return query.where({ hidden: false })
      }
      }

      Parameters

      • this: Decorators<TD>
      • opts: { default?: boolean } = {}

        — optional options

        • Optionaldefault?: boolean

          If true, this scope will be applied automatically to all queries involving this model. Defaults to false.

      Returns any

      A Scope decorator

    • Marks an integer column as a sortable position: Dream keeps the positions of every record in a sort scope contiguous, starting at 1, as records are created, moved, destroyed and undestroyed. A concurrent write racing a cascaded destroy or undestroy, under Cascaded destroy and undestroy below, is the one thing that can leave a scope with a gap; Model.resort('position') closes it.

      class Post extends ApplicationModel {
      @deco.Sortable({ scope: 'user' })
      public position: number
      }

      await post.update({ position: 2 }) // the records at 2 and above shift up

      A position past the end of the scope is clamped to the end, and a position below 1 — or none at all — lands the record at the end.

      A save that changes the sort scope ignores a position given alongside it. The record lands at the end of the scope it moves into, whatever position the same update supplied:

      await post.update({ user: otherUser, position: 1 })
      // post is now the last record in otherUser's scope, not the first

      Move it in two saves to place it: await post.update({ user: otherUser }), then await post.update({ position: 1 }).

      Sortable requires a query driver that supports advisory transaction locks — the PostgresQueryDriver does — since a position write serializes the writers of its sort scope on one, apart from the cascades described below. This concurrency guarantee first shipped in Dream 2.28.0 and begins only after every writer is running a lock-aware release. During the first rolling deployment, older processes take no advisory locks and can still race the upgraded processes.

      All participating Dream writers of one hot scope serialize. Ordinary saves and destroys with skipHooks, direct query writes, raw SQL, and older pre-lock Dream processes bypass Sortable maintenance and do not participate in its locking protocol; a cascaded destroy or undestroy performs its maintenance but does not participate either. A direct undestroy still performs stabilized Sortable maintenance and acquires scope locks with skipHooks: true; locked query batches likewise acquire their scope locks during preflight, before any per-record callbacks, even when those callbacks skip hooks. A waiter that enters the protocol holds a pooled connection until the holder finishes or sortableScopeLockTimeout expires; sustained contention can therefore produce latency waves, timeouts, and pool starvation. Keep database work inside the lock window short, and avoid cross-region database latency for hot scopes.

      Cascaded destroy and undestroy take no scope lock. A record destroyed or undestroyed because its owner was — reached through a dependent: 'destroy' association, or through the undestroy that restores one — does its position work without the advisory lock, so a cascade holds no lock per sort scope it touches, however many scopes that is. Calling destroy() or undestroy() on a record yourself is unchanged. Where the cascade is destroying the owner of a sort scope — a HasMany whose foreign key is one of the field's scope columns, with no condition and not through, not polymorphic, whose target is not an STI child and declares no default scope other than @deco.SoftDelete — every row of that scope goes with it, and the cascade neither reads nor compacts it. Any other cascaded destroy compacts the scope it vacates, and every cascaded undestroy appends at the end of its scope, exactly as the direct operations do, only without the lock.

      Without the lock, a writer of the same sort scope during the cascade — a create, a move, a destroy, a resort — is not kept out, which costs one of two things. A gap: positions stay unique and correctly ordered, and Model.resort('position') closes it. Or two live rows sharing a position, which the deferrable unique constraint refuses when the later of the two transactions reaches COMMIT — as a unique violation, or as a deadlock when both reach it together — so nothing half-done commits and no commit hook runs. A destroy() or undestroy() whose transaction Dream opened runs the whole operation again on such a refusal, up to three times, before letting the adapter's error escape. Inside a transaction you opened, that error is your COMMIT failing: retry the transaction from the beginning, as for a deadlock. The other writer can lose the same race and fail at its own COMMIT instead, an ordinary create or update included, and Dream does not run those again: a bounded, occasional failed request in one contended scope is the deliberate price of cascades that hold no locks, where the lock count would otherwise be set by the data. A sort scope with a nullable column has no such backstop unless its constraint is declared NULLS NOT DISTINCT, since a plain unique constraint never sees two NULLs as equal.

      SortableScopeDidNotStabilize and SortableScopeLockWaitTimedOut are the expected Dream errors an application may choose to retry. Database deadlocks remain native adapter errors (for example PostgreSQL code 40P01 or MySQL errno 1213) so their driver fields and stacks remain intact. A database deadlock aborts the whole transaction: retry by starting the transaction again from the beginning, never by continuing it or retrying only the failed statement. Recurrent deadlocks call for shorter transactions or a consistent multi-resource acquisition order; they do not by themselves prove Sortable caused the cycle.

      import {
      SortableScopeDidNotStabilize,
      SortableScopeLockWaitTimedOut,
      } from '@rvoh/dream/errors'

      const runTransaction = async () =>
      await ApplicationModel.transaction(async txn => {
      // bind every operation in the attempt to txn
      })

      try {
      await runTransaction()
      } catch (error) {
      const adapterDeadlock =
      (error as { code?: string }).code === '40P01' ||
      (error as { errno?: number }).errno === 1213
      const retryable =
      error instanceof SortableScopeDidNotStabilize ||
      error instanceof SortableScopeLockWaitTimedOut ||
      adapterDeadlock
      if (!retryable) throw error

      await runTransaction() // one whole-transaction retry
      }

      NOTE: the Sortable decorator may not be used in STI child models (it may be used in the STI base class)

      Parameters

      • this: Decorators<TD>
      • Optionalopts: SortableOptions<T>

        Configuration options for the sortable decorator

        • scope

          The column, association, or combination thereof which you would like to restrict the incrementing logic to. Can be a single column name, a single belongs-to association name, or an array of column/association names

      Returns any

      A Sortable decorator

    • The Validate decorator decorates a method to run before saving a model to the database.

      class Sandbag {
      @deco.Validate()
      public validateWeight(this: Sandbag) {
      if (!this.weight) return

      const undefinedOrNull: any[] = [undefined, null]
      if (!undefinedOrNull.includes(this.weightKgs))
      this.addError('weight', 'cannot include weightKgs AND weight')
      if (!undefinedOrNull.includes(this.weightTons))
      this.addError('weight', 'cannot include weightTons AND weight')
      }
      }

      Parameters

      Returns any

      A Validate decorator

    • The Validates decorator decorates a field to validate according to the specified validator and options.

      class Balloon {
      @deco.Validates('numericality', { min: 0, max: 100 })
      public volume: DreamColumn<Balloon, 'volume'>
      }

      Type Parameters

      • VT extends ValidationType
      • VTArgs extends string | RegExp | { max?: number; min?: number } | { max?: number; min: number }

      Parameters

      • this: Decorators<TD>
      • type: VT

        — the type of validation

      • Optionalargs: VTArgs

        — optional arguments specific to the type of validation

      Returns any

      A Validates decorator

    • The Virtual decorator enables setting of fields as if they corresponded to columns in the model's table so they can be passed to new, create, and update.

      For example, in the first example, below, one could call await bodyMeasurement.update({ lbs 180.1 }), and 180.1 will be passed into the lbs setter, which then translates lbs to grams to be stored in the grams column in the metrics table.

      And in the second example, below, one could call await user.update({ password }), and, in the BeforeSave lifecycle hook, the password would be hashed into hashedPassword. (This is just an example to illustrate using the Virtual decorator on a simple field; it might be better design to use the getter/setter pattern for password, with the getter simply returning undefined.)

      class BodyMeasurement {
      @deco.Virtual('number')
      public get lbs() {
      const self: User = this
      return gramsToLbs(self.getAttribute('grams') ?? 0)
      }

      public set lbs(lbs: number) {
      const self: User = this
      self.setAttribute('grams', lbsToGrams(lbs))
      }

      @deco.Virtual('number')
      public get kilograms() {
      const self: User = this
      return gramsToKilograms(self.getAttribute('grams') ?? 0)
      }

      public set kilograms(kg: number) {
      const self: User = this
      self.setAttribute('grams', kilogramsToGrams(kg))
      }
      }
      class User {
      @deco.Virtual('string')
      public password: string

      @deco.BeforeSave()
      public hasPassword() {
      this.setAttribute('hashedPassword', preferredHashingAlgorithm(this.password))
      }
      }

      Parameters

      Returns any

      A Virtual decorator