Instance

Валидация в Superstruct строится вокруг декларативного описания структур данных. Помимо примитивов, массивов и объектов, отдельный слой занимает проверка того, является ли значение экземпляром определённого конструктора. Для этого используется механизм, основанный на операторе instanceof, который инкапсулируется в специализированную структуру.

Такая проверка необходима в случаях, когда данные выходят за рамки JSON-подобных объектов и начинают включать поведение: классы, встроенные типы JavaScript, пользовательские сущности с методами и состоянием.


Сигнатура и базовое использование

В Superstruct для проверки экземпляров используется фабрика instance.

import { instance, assert } from 'superstruct'

class UserService {}

const UserServiceStruct = instance(UserService)

assert(new UserService(), UserServiceStruct) // проходит проверку
assert({}, UserServiceStruct) // ошибка валидации

Внутри реализуется стандартная проверка:

value instanceof Constructor

где Constructor — переданный аргумент.

Ключевая особенность заключается в том, что проверяется именно цепочка прототипов, а не структура объекта.


Работа с пользовательскими классами

Использование instance наиболее очевидно в контексте доменных моделей и сервисов.

class ApiClient {
  constructor(baseUrl) {
    this.baseUrl = baseUrl
  }

  request() {
    return this.baseUrl
  }
}

const ApiClientStruct = instance(ApiClient)

const client = new ApiClient('https://api.example.com')

Проверка гарантирует, что значение не просто похоже на объект клиента, а действительно создано через соответствующий конструктор:

assert(client, ApiClientStruct) // корректно
assert({ baseUrl: 'https://api.example.com' }, ApiClientStruct) // ошибка

Такой подход исключает подмену объектов с аналогичной формой, но отсутствующей логикой.


Поведение с наследованием

Особое значение имеет взаимодействие с наследованием. Проверка через instanceof учитывает цепочку прототипов, поэтому экземпляры дочерних классов проходят проверку базового конструктора.

class Base {}
class Derived extends Base {}

const BaseStruct = instance(Base)

assert(new Derived(), BaseStruct) // проходит проверку

Это поведение следует стандартной модели JavaScript и важно учитывать при проектировании структур: проверка не ограничивает строго конкретный класс, если используется наследование.


Встроенные типы JavaScript

Механизм instance применяется не только к пользовательским классам, но и к встроенным объектам языка.

Date

const DateStruct = instance(Date)

assert(new Date(), DateStruct) // успешно
assert(Date.now(), DateStruct) // ошибка

Значение должно быть именно объектом Date, а не числовым timestamp.

Error

const ErrorStruct = instance(Error)

assert(new Error('fail'), ErrorStruct) // проходит

Это особенно полезно при обработке результатов операций, где ошибки передаются как объекты, а не строки.

Map и Set

const MapStruct = instance(Map)
const SetStruct = instance(Set)

Такие структуры часто используются в более сложных слоях приложения, где требуется гарантировать сохранение поведения коллекций.


Комбинация с другими структурами

instance редко используется изолированно. Чаще он становится частью сложных схем, где данные комбинируются с объектными и примитивными типами.

import { object, string, number, instance } from 'superstruct'

class Logger {
  log(message) {
    console.log(message)
  }
}

const LoggerStruct = instance(Logger)

const ConfigStruct = object({
  name: string(),
  retries: number(),
  logger: LoggerStruct
})

Такая композиция позволяет явно отделить данные от поведенческих компонентов, сохраняя строгую типизацию на уровне выполнения.


Проверка значений в рантайме

Одним из практических сценариев является валидация зависимостей, передаваемых в функции или сервисы.

function createController(logger) {
  assert(logger, instance(Logger))

  return {
    handle() {
      logger.log('handled')
    }
  }
}

В этом случае контракт функции становится явным: переданный объект обязан быть полноценным экземпляром класса, а не имитацией.


Ограничения и особенности поведения

Использование instance связано с рядом технических ограничений Jav * aScript:

  • Потеря прототипа При сериализации и десериализации (например, через JSON) экземпляры классов превращаются в обычные объекты, и проверка перестаёт проходить.

  • Множественные контексты выполнения В окружениях с несколькими realm (iframe, worker) instanceof может давать неожиданные результаты, так как конструкторы считаются разными.

  • Невозможность структурной проверки Проверяется только происхождение объекта, но не его внутреннее состояние.

class A {}
const a = new A()

a.__proto__ = null
// теперь проверка instance не сработает корректно

Роль в архитектуре типизации на рантайме

Механизм проверки экземпляров занимает промежуточное положение между структурной валидацией и проверкой типов на уровне языка. Он используется там, где данные перестают быть просто данными и становятся носителями поведения.

В отличие от объектных схем, где важно соответствие полей, здесь ключевым фактором выступает происхождение объекта, что позволяет строго контролировать границы между слоями системы и предотвращать подмену компонентов, обладающих логикой.