code:
import type {
  CodeKeywordDefinition,
  KeywordErrorDefinition,
  ErrorObject,
  AnySchema,
} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, str} from "../../compile/codegen"
import {alwaysValidSchema} from "../../compile/util"
import {validateArray} from "../code"
import {validateAdditionalItems} from "./additionalItems"

export type ItemsError = ErrorObject<"items", {limit: number}, AnySchema>

const error: KeywordErrorDefinition = {
  message: ({params: {len}}) => str`must NOT have more than ${len} items`,
  params: ({params: {len}}) => _`{limit: ${len}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "items",
  type: "array",
  schemaType: ["object", "boolean"],
  before: "uniqueItems",
  error,
  code(cxt: KeywordCxt) {
    const {schema, parentSchema, it} = cxt
    const {prefixItems} = parentSchema
    it.items = true
    if (alwaysValidSchema(it, schema)) return
    if (prefixItems) validateAdditionalItems(cxt, prefixItems)
    else cxt.ok(validateArray(cxt))
  },
}

export default def


code:
import type {CodeKeywordDefinition, ErrorNoParams, AnySchema} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {alwaysValidSchema} from "../../compile/util"

export type NotKeywordError = ErrorNoParams<"not", AnySchema>

const def: CodeKeywordDefinition = {
  keyword: "not",
  schemaType: ["object", "boolean"],
  trackErrors: true,
  code(cxt: KeywordCxt) {
    const {gen, schema, it} = cxt
    if (alwaysValidSchema(it, schema)) {
      cxt.fail()
      return
    }

    const valid = gen.name("valid")
    cxt.subschema(
      {
        keyword: "not",
        compositeRule: true,
        createErrors: false,
        allErrors: false,
      },
      valid
    )

    cxt.failResult(
      valid,
      () => cxt.reset(),
      () => cxt.error()
    )
  },
  error: {message: "must NOT be valid"},
}

export default def

license: mit


code:
import type {
  CodeKeywordDefinition,
  ErrorObject,
  KeywordErrorDefinition,
  AnySchema,
} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, Name} from "../../compile/codegen"
import {alwaysValidSchema} from "../../compile/util"
import {SchemaCxt} from "../../compile"

export type OneOfError = ErrorObject<
  "oneOf",
  {passingSchemas: [number, number] | null},
  AnySchema[]
>

const error: KeywordErrorDefinition = {
  message: "must match exactly one schema in oneOf",
  params: ({params}) => _`{passingSchemas: ${params.passing}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "oneOf",
  schemaType: "array",
  trackErrors: true,
  error,
  code(cxt: KeywordCxt) {
    const {gen, schema, parentSchema, it} = cxt
    /* istanbul ignore if */
    if (!Array.isArray(schema)) throw new Error("ajv implementation error")
    if (it.opts.discriminator && parentSchema.discriminator) return
    const schArr: AnySchema[] = schema
    const valid = gen.let("valid", false)
    const passing = gen.let("passing", null)
    const schValid = gen.name("_valid")
    cxt.setParams({passing})
    // TODO possibly fail straight away (with warning or exception) if there are two empty always valid schemas

    gen.block(validateOneOf)

    cxt.result(
      valid,
      () => cxt.reset(),
      () => cxt.error(true)
    )

    function validateOneOf(): void {
      schArr.forEach((sch: AnySchema, i: number) => {
        let schCxt: SchemaCxt | undefined
        if (alwaysValidSchema(it, sch)) {
          gen.var(schValid, true)
        } else {
          schCxt = cxt.subschema(
            {
              keyword: "oneOf",
              schemaProp: i,
              compositeRule: true,
            },
            schValid
          )
        }

        if (i > 0) {
          gen
            .if(_`${schValid} && ${valid}`)
            .assign(valid, false)
            .assign(passing, _`[${passing}, ${i}]`)
            .else()
        }

        gen.if(schValid, () => {
          gen.assign(valid, true)
          gen.assign(passing, i)
          if (schCxt) cxt.mergeEvaluated(schCxt, Name)
        })
      })
    }
  },
}

export default def


code:
import type {CodeKeywordDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {allSchemaProperties, usePattern} from "../code"
import {_, not, Name} from "../../compile/codegen"
import {alwaysValidSchema, checkStrictMode} from "../../compile/util"
import {evaluatedPropsToName, Type} from "../../compile/util"
import {AnySchema} from "../../types"

const def: CodeKeywordDefinition = {
  keyword: "patternProperties",
  type: "object",
  schemaType: "object",
  code(cxt: KeywordCxt) {
    const {gen, schema, data, parentSchema, it} = cxt
    const {opts} = it
    const patterns = allSchemaProperties(schema)
    const alwaysValidPatterns = patterns.filter((p) =>
      alwaysValidSchema(it, schema[p] as AnySchema)
    )

    if (
      patterns.length === 0 ||
      (alwaysValidPatterns.length === patterns.length &&
        (!it.opts.unevaluated || it.props === true))
    ) {
      return
    }

    const checkProperties =
      opts.strictSchema && !opts.allowMatchingProperties && parentSchema.properties
    const valid = gen.name("valid")
    if (it.props !== true && !(it.props instanceof Name)) {
      it.props = evaluatedPropsToName(gen, it.props)
    }
    const {props} = it
    validatePatternProperties()

    function validatePatternProperties(): void {
      for (const pat of patterns) {
        if (checkProperties) checkMatchingProperties(pat)
        if (it.allErrors) {
          validateProperties(pat)
        } else {
          gen.var(valid, true) // TODO var
          validateProperties(pat)
          gen.if(valid)
        }
      }
    }

    function checkMatchingProperties(pat: string): void {
      for (const prop in checkProperties) {
        if (new RegExp(pat).test(prop)) {
          checkStrictMode(
            it,
            `property ${prop} matches pattern ${pat} (use allowMatchingProperties)`
          )
        }
      }
    }

    function validateProperties(pat: string): void {
      gen.forIn("key", data, (key) => {
        gen.if(_`${usePattern(cxt, pat)}.test(${key})`, () => {
          const alwaysValid = alwaysValidPatterns.includes(pat)
          if (!alwaysValid) {
            cxt.subschema(
              {
                keyword: "patternProperties",
                schemaProp: pat,
                dataProp: key,
                dataPropType: Type.Str,
              },
              valid
            )
          }

          if (it.opts.unevaluated && props !== true) {
            gen.assign(_`${props}[${key}]`, true)
          } else if (!alwaysValid && !it.allErrors) {
            // can short-circuit if `unevaluatedProperties` is not supported (opts.next === false)
            // or if all properties were evaluated (props === true)
            gen.if(not(valid), () => gen.break())
          }
        })
      })
    }
  },
}

export default def


code:
import type {CodeKeywordDefinition} from "../../types"
import {validateTuple} from "./items"

const def: CodeKeywordDefinition = {
  keyword: "prefixItems",
  type: "array",
  schemaType: ["array"],
  before: "uniqueItems",
  code: (cxt) => validateTuple(cxt, "items"),
}

export default def


code:
import type {CodeKeywordDefinition} from "../../types"
import {KeywordCxt} from "../../compile/validate"
import {propertyInData, allSchemaProperties} from "../code"
import {alwaysValidSchema, toHash, mergeEvaluated} from "../../compile/util"
import apDef from "./additionalProperties"

const def: CodeKeywordDefinition = {
  keyword: "properties",
  type: "object",
  schemaType: "object",
  code(cxt: KeywordCxt) {
    const {gen, schema, parentSchema, data, it} = cxt
    if (it.opts.removeAdditional === "all" && parentSchema.additionalProperties === undefined) {
      apDef.code(new KeywordCxt(it, apDef, "additionalProperties"))
    }
    const allProps = allSchemaProperties(schema)
    for (const prop of allProps) {
      it.definedProperties.add(prop)
    }
    if (it.opts.unevaluated && allProps.length && it.props !== true) {
      it.props = mergeEvaluated.props(gen, toHash(allProps), it.props)
    }
    const properties = allProps.filter((p) => !alwaysValidSchema(it, schema[p]))
    if (properties.length === 0) return
    const valid = gen.name("valid")

    for (const prop of properties) {
      if (hasDefault(prop)) {
        applyPropertySchema(prop)
      } else {
        gen.if(propertyInData(gen, data, prop, it.opts.ownProperties))
        applyPropertySchema(prop)
        if (!it.allErrors) gen.else().var(valid, true)
        gen.endIf()
      }
      cxt.it.definedProperties.add(prop)
      cxt.ok(valid)
    }

    function hasDefault(prop: string): boolean | undefined {
      return it.opts.useDefaults && !it.compositeRule && schema[prop].default !== undefined
    }

    function applyPropertySchema(prop: string): void {
      cxt.subschema(
        {
          keyword: "properties",
          schemaProp: prop,
          dataProp: prop,
        },
        valid
      )
    }
  },
}

export default def


code:
import type {
  CodeKeywordDefinition,
  ErrorObject,
  KeywordErrorDefinition,
  AnySchema,
} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, not} from "../../compile/codegen"
import {alwaysValidSchema} from "../../compile/util"

export type PropertyNamesError = ErrorObject<"propertyNames", {propertyName: string}, AnySchema>

const error: KeywordErrorDefinition = {
  message: "property name must be valid",
  params: ({params}) => _`{propertyName: ${params.propertyName}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "propertyNames",
  type: "object",
  schemaType: ["object", "boolean"],
  error,
  code(cxt: KeywordCxt) {
    const {gen, schema, data, it} = cxt
    if (alwaysValidSchema(it, schema)) return
    const valid = gen.name("valid")

    gen.forIn("key", data, (key) => {
      cxt.setParams({propertyName: key})
      cxt.subschema(
        {
          keyword: "propertyNames",
          data: key,
          dataTypes: ["string"],
          propertyName: key,
          compositeRule: true,
        },
        valid
      )
      gen.if(not(valid), () => {
        cxt.error(true)
        if (!it.allErrors) gen.break()
      })
    })

    cxt.ok(valid)
  },
}

export default def


code:
import type {CodeKeywordDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {checkStrictMode} from "../../compile/util"

const def: CodeKeywordDefinition = {
  keyword: ["then", "else"],
  schemaType: ["object", "boolean"],
  code({keyword, parentSchema, it}: KeywordCxt) {
    if (parentSchema.if === undefined) checkStrictMode(it, `"${keyword}" without "if" is ignored`)
  },
}

export default def


code:
import type {AnySchema, SchemaMap} from "../types"
import type {SchemaCxt} from "../compile"
import type {KeywordCxt} from "../compile/validate"
import {CodeGen, _, and, or, not, nil, strConcat, getProperty, Code, Name} from "../compile/codegen"
import {alwaysValidSchema, Type} from "../compile/util"
import N from "../compile/names"
import {useFunc} from "../compile/util"
export function checkReportMissingProp(cxt: KeywordCxt, prop: string): void {
  const {gen, data, it} = cxt
  gen.if(noPropertyInData(gen, data, prop, it.opts.ownProperties), () => {
    cxt.setParams({missingProperty: _`${prop}`}, true)
    cxt.error()
  })
}

export function checkMissingProp(
  {gen, data, it: {opts}}: KeywordCxt,
  properties: string[],
  missing: Name
): Code {
  return or(
    ...properties.map((prop) =>
      and(noPropertyInData(gen, data, prop, opts.ownProperties), _`${missing} = ${prop}`)
    )
  )
}

export function reportMissingProp(cxt: KeywordCxt, missing: Name): void {
  cxt.setParams({missingProperty: missing}, true)
  cxt.error()
}

export function hasPropFunc(gen: CodeGen): Name {
  return gen.scopeValue("func", {
    // eslint-disable-next-line @typescript-eslint/unbound-method
    ref: Object.prototype.hasOwnProperty,
    code: _`Object.prototype.hasOwnProperty`,
  })
}

export function isOwnProperty(gen: CodeGen, data: Name, property: Name | string): Code {
  return _`${hasPropFunc(gen)}.call(${data}, ${property})`
}

export function propertyInData(
  gen: CodeGen,
  data: Name,
  property: Name | string,
  ownProperties?: boolean
): Code {
  const cond = _`${data}${getProperty(property)} !== undefined`
  return ownProperties ? _`${cond} && ${isOwnProperty(gen, data, property)}` : cond
}

export function noPropertyInData(
  gen: CodeGen,
  data: Name,
  property: Name | string,
  ownProperties?: boolean
): Code {
  const cond = _`${data}${getProperty(property)} === undefined`
  return ownProperties ? or(cond, not(isOwnProperty(gen, data, property))) : cond
}

export function allSchemaProperties(schemaMap?: SchemaMap): string[] {
  return schemaMap ? Object.keys(schemaMap).filter((p) => p !== "__proto__") : []
}

export function schemaProperties(it: SchemaCxt, schemaMap: SchemaMap): string[] {
  return allSchemaProperties(schemaMap).filter(
    (p) => !alwaysValidSchema(it, schemaMap[p] as AnySchema)
  )
}

export function callValidateCode(
  {schemaCode, data, it: {gen, topSchemaRef, schemaPath, errorPath}, it}: KeywordCxt,
  func: Code,
  context: Code,
  passSchema?: boolean
): Code {
  const dataAndSchema = passSchema ? _`${schemaCode}, ${data}, ${topSchemaRef}${schemaPath}` : data
  const valCxt: [Name, Code | number][] = [
    [N.instancePath, strConcat(N.instancePath, errorPath)],
    [N.parentData, it.parentData],
    [N.parentDataProperty, it.parentDataProperty],
    [N.rootData, N.rootData],
  ]
  if (it.opts.dynamicRef) valCxt.push([N.dynamicAnchors, N.dynamicAnchors])
  const args = _`${dataAndSchema}, ${gen.object(...valCxt)}`
  return context !== nil ? _`${func}.call(${context}, ${args})` : _`${func}(${args})`
}

const newRegExp = _`new RegExp`

export function usePattern({gen, it: {opts}}: KeywordCxt, pattern: string): Name {
  const u = opts.unicodeRegExp ? "u" : ""
  const {regExp} = opts.code
  const rx = regExp(pattern, u)

  return gen.scopeValue("pattern", {
    key: rx.toString(),
    ref: rx,
    code: _`${regExp.code === "new RegExp" ? newRegExp : useFunc(gen, regExp)}(${pattern}, ${u})`,
  })
}

export function validateArray(cxt: KeywordCxt): Name {
  const {gen, data, keyword, it} = cxt
  const valid = gen.name("valid")
  if (it.allErrors) {
    const validArr = gen.let("valid", true)
    validateItems(() => gen.assign(validArr, false))
    return validArr
  }
  gen.var(valid, true)
  validateItems(() => gen.break())
  return valid

  function validateItems(notValid: () => void): void {
    const len = gen.const("len", _`${data}.length`)
    gen.forRange("i", 0, len, (i) => {
      cxt.subschema(
        {
          keyword,
          dataProp: i,
          dataPropType: Type.Num,
        },
        valid
      )
      gen.if(not(valid), notValid)
    })
  }
}

export function validateUnion(cxt: KeywordCxt): void {
  const {gen, schema, keyword, it} = cxt
  /* istanbul ignore if */
  if (!Array.isArray(schema)) throw new Error("ajv implementation error")
  const alwaysValid = schema.some((sch: AnySchema) => alwaysValidSchema(it, sch))
  if (alwaysValid && !it.opts.unevaluated) return

  const valid = gen.let("valid", false)
  const schValid = gen.name("_valid")

  gen.block(() =>
    schema.forEach((_sch: AnySchema, i: number) => {
      const schCxt = cxt.subschema(
        {
          keyword,
          schemaProp: i,
          compositeRule: true,
        },
        schValid
      )
      gen.assign(valid, _`${valid} || ${schValid}`)
      const merged = cxt.mergeValidEvaluated(schCxt, schValid)
      // can short-circuit if `unevaluatedProperties/Items` not supported (opts.unevaluated !== true)
      // or if all properties and items were evaluated (it.props === true && it.items === true)
      if (!merged) gen.if(not(valid))
    })
  )

  cxt.result(
    valid,
    () => cxt.reset(),
    () => cxt.error(true)
  )
}


code:
import type {CodeKeywordDefinition} from "../../types"

const def: CodeKeywordDefinition = {
  keyword: "id",
  code() {
    throw new Error('NOT SUPPORTED: keyword "id", use "$id" for schema ID')
  },
}

export default def


code:
import type {Vocabulary} from "../../types"
import idKeyword from "./id"
import refKeyword from "./ref"

const core: Vocabulary = [
  "$schema",
  "$id",
  "$defs",
  "$vocabulary",
  {keyword: "$comment"},
  "definitions",
  idKeyword,
  refKeyword,
]

export default core


code:
import type {CodeKeywordDefinition, AnySchema} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import MissingRefError from "../../compile/ref_error"
import {callValidateCode} from "../code"
import {_, nil, stringify, Code, Name} from "../../compile/codegen"
import N from "../../compile/names"
import {SchemaEnv, resolveRef} from "../../compile"
import {mergeEvaluated} from "../../compile/util"

const def: CodeKeywordDefinition = {
  keyword: "$ref",
  schemaType: "string",
  code(cxt: KeywordCxt): void {
    const {gen, schema: $ref, it} = cxt
    const {baseId, schemaEnv: env, validateName, opts, self} = it
    const {root} = env
    if (($ref === "#" || $ref === "#/") && baseId === root.baseId) return callRootRef()
    const schOrEnv = resolveRef.call(self, root, baseId, $ref)
    if (schOrEnv === undefined) throw new MissingRefError(it.opts.uriResolver, baseId, $ref)
    if (schOrEnv instanceof SchemaEnv) return callValidate(schOrEnv)
    return inlineRefSchema(schOrEnv)

    function callRootRef(): void {
      if (env === root) return callRef(cxt, validateName, env, env.$async)
      const rootName = gen.scopeValue("root", {ref: root})
      return callRef(cxt, _`${rootName}.validate`, root, root.$async)
    }

    function callValidate(sch: SchemaEnv): void {
      const v = getValidate(cxt, sch)
      callRef(cxt, v, sch, sch.$async)
    }

    function inlineRefSchema(sch: AnySchema): void {
      const schName = gen.scopeValue(
        "schema",
        opts.code.source === true ? {ref: sch, code: stringify(sch)} : {ref: sch}
      )
      const valid = gen.name("valid")
      const schCxt = cxt.subschema(
        {
          schema: sch,
          dataTypes: [],
          schemaPath: nil,
          topSchemaRef: schName,
          errSchemaPath: $ref,
        },
        valid
      )
      cxt.mergeEvaluated(schCxt)
      cxt.ok(valid)
    }
  },
}

export function getValidate(cxt: KeywordCxt, sch: SchemaEnv): Code {
  const {gen} = cxt
  return sch.validate
    ? gen.scopeValue("validate", {ref: sch.validate})
    : _`${gen.scopeValue("wrapper", {ref: sch})}.validate`
}

export function callRef(cxt: KeywordCxt, v: Code, sch?: SchemaEnv, $async?: boolean): void {
  const {gen, it} = cxt
  const {allErrors, schemaEnv: env, opts} = it
  const passCxt = opts.passContext ? N.this : nil
  if ($async) callAsyncRef()
  else callSyncRef()

  function callAsyncRef(): void {
    if (!env.$async) throw new Error("async schema referenced by sync schema")
    const valid = gen.let("valid")
    gen.try(
      () => {
        gen.code(_`await ${callValidateCode(cxt, v, passCxt)}`)
        addEvaluatedFrom(v) // TODO will not work with async, it has to be returned with the result
        if (!allErrors) gen.assign(valid, true)
      },
      (e) => {
        gen.if(_`!(${e} instanceof ${it.ValidationError as Name})`, () => gen.throw(e))
        addErrorsFrom(e)
        if (!allErrors) gen.assign(valid, false)
      }
    )
    cxt.ok(valid)
  }

  function callSyncRef(): void {
    cxt.result(
      callValidateCode(cxt, v, passCxt),
      () => addEvaluatedFrom(v),
      () => addErrorsFrom(v)
    )
  }

  function addErrorsFrom(source: Code): void {
    const errs = _`${source}.errors`
    gen.assign(N.vErrors, _`${N.vErrors} === null ? ${errs} : ${N.vErrors}.concat(${errs})`) // TODO tagged
    gen.assign(N.errors, _`${N.vErrors}.length`)
  }

  function addEvaluatedFrom(source: Code): void {
    if (!it.opts.unevaluated) return
    const schEvaluated = sch?.validate?.evaluated
    // TODO refactor
    if (it.props !== true) {
      if (schEvaluated && !schEvaluated.dynamicProps) {
        if (schEvaluated.props !== undefined) {
          it.props = mergeEvaluated.props(gen, schEvaluated.props, it.props)
        }
      } else {
        const props = gen.var("props", _`${source}.evaluated.props`)
        it.props = mergeEvaluated.props(gen, props, it.props, Name)
      }
    }
    if (it.items !== true) {
      if (schEvaluated && !schEvaluated.dynamicItems) {
        if (schEvaluated.items !== undefined) {
          it.items = mergeEvaluated.items(gen, schEvaluated.items, it.items)
        }
      } else {
        const items = gen.var("items", _`${source}.evaluated.items`)
        it.items = mergeEvaluated.items(gen, items, it.items, Name)
      }
    }
  }
}

export default def


code:
import type {CodeKeywordDefinition, AnySchemaObject, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, getProperty, Name} from "../../compile/codegen"
import {DiscrError, DiscrErrorObj} from "../discriminator/types"
import {resolveRef, SchemaEnv} from "../../compile"
import MissingRefError from "../../compile/ref_error"
import {schemaHasRulesButRef} from "../../compile/util"

export type DiscriminatorError = DiscrErrorObj<DiscrError.Tag> | DiscrErrorObj<DiscrError.Mapping>

const error: KeywordErrorDefinition = {
  message: ({params: {discrError, tagName}}) =>
    discrError === DiscrError.Tag
      ? `tag "${tagName}" must be string`
      : `value of tag "${tagName}" must be in oneOf`,
  params: ({params: {discrError, tag, tagName}}) =>
    _`{error: ${discrError}, tag: ${tagName}, tagValue: ${tag}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "discriminator",
  type: "object",
  schemaType: "object",
  error,
  code(cxt: KeywordCxt) {
    const {gen, data, schema, parentSchema, it} = cxt
    const {oneOf} = parentSchema
    if (!it.opts.discriminator) {
      throw new Error("discriminator: requires discriminator option")
    }
    const tagName = schema.propertyName
    if (typeof tagName != "string") throw new Error("discriminator: requires propertyName")
    if (schema.mapping) throw new Error("discriminator: mapping is not supported")
    if (!oneOf) throw new Error("discriminator: requires oneOf keyword")
    const valid = gen.let("valid", false)
    const tag = gen.const("tag", _`${data}${getProperty(tagName)}`)
    gen.if(
      _`typeof ${tag} == "string"`,
      () => validateMapping(),
      () => cxt.error(false, {discrError: DiscrError.Tag, tag, tagName})
    )
    cxt.ok(valid)

    function validateMapping(): void {
      const mapping = getMapping()
      gen.if(false)
      for (const tagValue in mapping) {
        gen.elseIf(_`${tag} === ${tagValue}`)
        gen.assign(valid, applyTagSchema(mapping[tagValue]))
      }
      gen.else()
      cxt.error(false, {discrError: DiscrError.Mapping, tag, tagName})
      gen.endIf()
    }

    function applyTagSchema(schemaProp?: number): Name {
      const _valid = gen.name("valid")
      const schCxt = cxt.subschema({keyword: "oneOf", schemaProp}, _valid)
      cxt.mergeEvaluated(schCxt, Name)
      return _valid
    }

    function getMapping(): {[T in string]?: number} {
      const oneOfMapping: {[T in string]?: number} = {}
      const topRequired = hasRequired(parentSchema)
      let tagRequired = true
      for (let i = 0; i < oneOf.length; i++) {
        let sch = oneOf[i]
        if (sch?.$ref && !schemaHasRulesButRef(sch, it.self.RULES)) {
          const ref = sch.$ref
          sch = resolveRef.call(it.self, it.schemaEnv.root, it.baseId, ref)
          if (sch instanceof SchemaEnv) sch = sch.schema
          if (sch === undefined) throw new MissingRefError(it.opts.uriResolver, it.baseId, ref)
        }
        const propSch = sch?.properties?.[tagName]
        if (typeof propSch != "object") {
          throw new Error(
            `discriminator: oneOf subschemas (or referenced schemas) must have "properties/${tagName}"`
          )
        }
        tagRequired = tagRequired && (topRequired || hasRequired(sch))
        addMappings(propSch, i)
      }
      if (!tagRequired) throw new Error(`discriminator: "${tagName}" must be required`)
      return oneOfMapping

      function hasRequired({required}: AnySchemaObject): boolean {
        return Array.isArray(required) && required.includes(tagName)
      }

      function addMappings(sch: AnySchemaObject, i: number): void {
        if (sch.const) {
          addMapping(sch.const, i)
        } else if (sch.enum) {
          for (const tagValue of sch.enum) {
            addMapping(tagValue, i)
          }
        } else {
          throw new Error(`discriminator: "properties/${tagName}" must have "const" or "enum"`)
        }
      }

      function addMapping(tagValue: unknown, i: number): void {
        if (typeof tagValue != "string" || tagValue in oneOfMapping) {
          throw new Error(`discriminator: "${tagName}" values must be unique strings`)
        }
        oneOfMapping[tagValue] = i
      }
    }
  },
}

export default def


code:
import type {ErrorObject} from "../../types"

export enum DiscrError {
  Tag = "tag",
  Mapping = "mapping",
}

export type DiscrErrorObj<E extends DiscrError> = ErrorObject<
  "discriminator",
  {error: E; tag: string; tagValue: unknown},
  string
>


code:
import type {Vocabulary} from "../types"
import coreVocabulary from "./core"
import validationVocabulary from "./validation"
import getApplicatorVocabulary from "./applicator"
import dynamicVocabulary from "./dynamic"
import nextVocabulary from "./next"
import unevaluatedVocabulary from "./unevaluated"
import formatVocabulary from "./format"
import {metadataVocabulary, contentVocabulary} from "./metadata"

const draft2020Vocabularies: Vocabulary[] = [
  dynamicVocabulary,
  coreVocabulary,
  validationVocabulary,
  getApplicatorVocabulary(true),
  formatVocabulary,
  metadataVocabulary,
  contentVocabulary,
  nextVocabulary,
  unevaluatedVocabulary,
]

export default draft2020Vocabularies


code:
import type {Vocabulary} from "../types"
import coreVocabulary from "./core"
import validationVocabulary from "./validation"
import getApplicatorVocabulary from "./applicator"
import formatVocabulary from "./format"
import {metadataVocabulary, contentVocabulary} from "./metadata"

const draft7Vocabularies: Vocabulary[] = [
  coreVocabulary,
  validationVocabulary,
  getApplicatorVocabulary(),
  formatVocabulary,
  metadataVocabulary,
  contentVocabulary,
]

export default draft7Vocabularies


code:
import type {CodeKeywordDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, getProperty, Code} from "../../compile/codegen"
import N from "../../compile/names"
import {SchemaEnv, compileSchema} from "../../compile"
import {getValidate} from "../core/ref"

const def: CodeKeywordDefinition = {
  keyword: "$dynamicAnchor",
  schemaType: "string",
  code: (cxt) => dynamicAnchor(cxt, cxt.schema),
}

export function dynamicAnchor(cxt: KeywordCxt, anchor: string): void {
  const {gen, it} = cxt
  it.schemaEnv.root.dynamicAnchors[anchor] = true
  const v = _`${N.dynamicAnchors}${getProperty(anchor)}`
  const validate = it.errSchemaPath === "#" ? it.validateName : _getValidate(cxt)
  gen.if(_`!${v}`, () => gen.assign(v, validate))
}

function _getValidate(cxt: KeywordCxt): Code {
  const {schemaEnv, schema, self} = cxt.it
  const {root, baseId, localRefs, meta} = schemaEnv.root
  const {schemaId} = self.opts
  const sch = new SchemaEnv({schema, schemaId, root, baseId, localRefs, meta})
  compileSchema.call(self, sch)
  return getValidate(cxt, sch)
}

export default def


code:
import type {CodeKeywordDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, getProperty, Code, Name} from "../../compile/codegen"
import N from "../../compile/names"
import {callRef} from "../core/ref"

const def: CodeKeywordDefinition = {
  keyword: "$dynamicRef",
  schemaType: "string",
  code: (cxt) => dynamicRef(cxt, cxt.schema),
}

export function dynamicRef(cxt: KeywordCxt, ref: string): void {
  const {gen, keyword, it} = cxt
  if (ref[0] !== "#") throw new Error(`"${keyword}" only supports hash fragment reference`)
  const anchor = ref.slice(1)
  if (it.allErrors) {
    _dynamicRef()
  } else {
    const valid = gen.let("valid", false)
    _dynamicRef(valid)
    cxt.ok(valid)
  }

  function _dynamicRef(valid?: Name): void {
    // TODO the assumption here is that `recursiveRef: #` always points to the root
    // of the schema object, which is not correct, because there may be $id that
    // makes # point to it, and the target schema may not contain dynamic/recursiveAnchor.
    // Because of that 2 tests in recursiveRef.json fail.
    // This is a similar problem to #815 (`$id` doesn't alter resolution scope for `{ "$ref": "#" }`).
    // (This problem is not tested in JSON-Schema-Test-Suite)
    if (it.schemaEnv.root.dynamicAnchors[anchor]) {
      const v = gen.let("_v", _`${N.dynamicAnchors}${getProperty(anchor)}`)
      gen.if(v, _callRef(v, valid), _callRef(it.validateName, valid))
    } else {
      _callRef(it.validateName, valid)()
    }
  }

  function _callRef(validate: Code, valid?: Name): () => void {
    return valid
      ? () =>
          gen.block(() => {
            callRef(cxt, validate)
            gen.let(valid, true)
          })
      : () => callRef(cxt, validate)
  }
}

export default def


code:
import type {Vocabulary} from "../../types"
import dynamicAnchor from "./dynamicAnchor"
import dynamicRef from "./dynamicRef"
import recursiveAnchor from "./recursiveAnchor"
import recursiveRef from "./recursiveRef"

const dynamic: Vocabulary = [dynamicAnchor, dynamicRef, recursiveAnchor, recursiveRef]

export default dynamic


code:
import type {CodeKeywordDefinition} from "../../types"
import {dynamicAnchor} from "./dynamicAnchor"
import {checkStrictMode} from "../../compile/util"

const def: CodeKeywordDefinition = {
  keyword: "$recursiveAnchor",
  schemaType: "boolean",
  code(cxt) {
    if (cxt.schema) dynamicAnchor(cxt, "")
    else checkStrictMode(cxt.it, "$recursiveAnchor: false is ignored")
  },
}

export default def


code:
import type {CodeKeywordDefinition} from "../../types"
import {dynamicRef} from "./dynamicRef"

const def: CodeKeywordDefinition = {
  keyword: "$recursiveRef",
  schemaType: "string",
  code: (cxt) => dynamicRef(cxt, cxt.schema),
}

export default def


code:
import type {TypeError} from "../compile/validate/dataType"
import type {ApplicatorKeywordError} from "./applicator"
import type {ValidationKeywordError} from "./validation"
import type {FormatError} from "./format/format"
import type {UnevaluatedPropertiesError} from "./unevaluated/unevaluatedProperties"
import type {UnevaluatedItemsError} from "./unevaluated/unevaluatedItems"
import type {DependentRequiredError} from "./validation/dependentRequired"
import type {DiscriminatorError} from "./discriminator"

export type DefinedError =
  | TypeError
  | ApplicatorKeywordError
  | ValidationKeywordError
  | FormatError
  | UnevaluatedPropertiesError
  | UnevaluatedItemsError
  | DependentRequiredError
  | DiscriminatorError


code:
import type {
  AddedFormat,
  FormatValidator,
  AsyncFormatValidator,
  CodeKeywordDefinition,
  KeywordErrorDefinition,
  ErrorObject,
} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, str, nil, or, Code, getProperty, regexpCode} from "../../compile/codegen"

type FormatValidate =
  | FormatValidator<string>
  | FormatValidator<number>
  | AsyncFormatValidator<string>
  | AsyncFormatValidator<number>
  | RegExp
  | string
  | true

export type FormatError = ErrorObject<"format", {format: string}, string | {$data: string}>

const error: KeywordErrorDefinition = {
  message: ({schemaCode}) => str`must match format "${schemaCode}"`,
  params: ({schemaCode}) => _`{format: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "format",
  type: ["number", "string"],
  schemaType: "string",
  $data: true,
  error,
  code(cxt: KeywordCxt, ruleType?: string) {
    const {gen, data, $data, schema, schemaCode, it} = cxt
    const {opts, errSchemaPath, schemaEnv, self} = it
    if (!opts.validateFormats) return

    if ($data) validate$DataFormat()
    else validateFormat()

    function validate$DataFormat(): void {
      const fmts = gen.scopeValue("formats", {
        ref: self.formats,
        code: opts.code.formats,
      })
      const fDef = gen.const("fDef", _`${fmts}[${schemaCode}]`)
      const fType = gen.let("fType")
      const format = gen.let("format")
      // TODO simplify
      gen.if(
        _`typeof ${fDef} == "object" && !(${fDef} instanceof RegExp)`,
        () => gen.assign(fType, _`${fDef}.type || "string"`).assign(format, _`${fDef}.validate`),
        () => gen.assign(fType, _`"string"`).assign(format, fDef)
      )
      cxt.fail$data(or(unknownFmt(), invalidFmt()))

      function unknownFmt(): Code {
        if (opts.strictSchema === false) return nil
        return _`${schemaCode} && !${format}`
      }

      function invalidFmt(): Code {
        const callFormat = schemaEnv.$async
          ? _`(${fDef}.async ? await ${format}(${data}) : ${format}(${data}))`
          : _`${format}(${data})`
        const validData = _`(typeof ${format} == "function" ? ${callFormat} : ${format}.test(${data}))`
        return _`${format} && ${format} !== true && ${fType} === ${ruleType} && !${validData}`
      }
    }

    function validateFormat(): void {
      const formatDef: AddedFormat | undefined = self.formats[schema]
      if (!formatDef) {
        unknownFormat()
        return
      }
      if (formatDef === true) return
      const [fmtType, format, fmtRef] = getFormat(formatDef)
      if (fmtType === ruleType) cxt.pass(validCondition())

      function unknownFormat(): void {
        if (opts.strictSchema === false) {
          self.logger.warn(unknownMsg())
          return
        }
        throw new Error(unknownMsg())

        function unknownMsg(): string {
          return `unknown format "${schema as string}" ignored in schema at path "${errSchemaPath}"`
        }
      }

      function getFormat(fmtDef: AddedFormat): [string, FormatValidate, Code] {
        const code =
          fmtDef instanceof RegExp
            ? regexpCode(fmtDef)
            : opts.code.formats
            ? _`${opts.code.formats}${getProperty(schema)}`
            : undefined
        const fmt = gen.scopeValue("formats", {key: schema, ref: fmtDef, code})
        if (typeof fmtDef == "object" && !(fmtDef instanceof RegExp)) {
          return [fmtDef.type || "string", fmtDef.validate, _`${fmt}.validate`]
        }

        return ["string", fmtDef, fmt]
      }

      function validCondition(): Code {
        if (typeof formatDef == "object" && !(formatDef instanceof RegExp) && formatDef.async) {
          if (!schemaEnv.$async) throw new Error("async format in sync schema")
          return _`await ${fmtRef}(${data})`
        }
        return typeof format == "function" ? _`${fmtRef}(${data})` : _`${fmtRef}.test(${data})`
      }
    }
  },
}

export default def


code:
import type {Vocabulary} from "../../types"
import formatKeyword from "./format"

const format: Vocabulary = [formatKeyword]

export default format


code:
import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, not, getProperty, Name} from "../../compile/codegen"
import {checkMetadata} from "./metadata"
import {checkNullableObject} from "./nullable"
import {typeErrorMessage, typeErrorParams, _JTDTypeError} from "./error"
import {DiscrError, DiscrErrorObj} from "../discriminator/types"

export type JTDDiscriminatorError =
  | _JTDTypeError<"discriminator", "object", string>
  | DiscrErrorObj<DiscrError.Tag>
  | DiscrErrorObj<DiscrError.Mapping>

const error: KeywordErrorDefinition = {
  message: (cxt) => {
    const {schema, params} = cxt
    return params.discrError
      ? params.discrError === DiscrError.Tag
        ? `tag "${schema}" must be string`
        : `value of tag "${schema}" must be in mapping`
      : typeErrorMessage(cxt, "object")
  },
  params: (cxt) => {
    const {schema, params} = cxt
    return params.discrError
      ? _`{error: ${params.discrError}, tag: ${schema}, tagValue: ${params.tag}}`
      : typeErrorParams(cxt, "object")
  },
}

const def: CodeKeywordDefinition = {
  keyword: "discriminator",
  schemaType: "string",
  implements: ["mapping"],
  error,
  code(cxt: KeywordCxt) {
    checkMetadata(cxt)
    const {gen, data, schema, parentSchema} = cxt
    const [valid, cond] = checkNullableObject(cxt, data)

    gen.if(cond)
    validateDiscriminator()
    gen.elseIf(not(valid))
    cxt.error()
    gen.endIf()
    cxt.ok(valid)

    function validateDiscriminator(): void {
      const tag = gen.const("tag", _`${data}${getProperty(schema)}`)
      gen.if(_`${tag} === undefined`)
      cxt.error(false, {discrError: DiscrError.Tag, tag})
      gen.elseIf(_`typeof ${tag} == "string"`)
      validateMapping(tag)
      gen.else()
      cxt.error(false, {discrError: DiscrError.Tag, tag}, {instancePath: schema})
      gen.endIf()
    }

    function validateMapping(tag: Name): void {
      gen.if(false)
      for (const tagValue in parentSchema.mapping) {
        gen.elseIf(_`${tag} === ${tagValue}`)
        gen.assign(valid, applyTagSchema(tagValue))
      }
      gen.else()
      cxt.error(
        false,
        {discrError: DiscrError.Mapping, tag},
        {instancePath: schema, schemaPath: "mapping", parentSchema: true}
      )
      gen.endIf()
    }

    function applyTagSchema(schemaProp: string): Name {
      const _valid = gen.name("valid")
      cxt.subschema(
        {
          keyword: "mapping",
          schemaProp,
          jtdDiscriminator: schema,
        },
        _valid
      )
      return _valid
    }
  },
}

export default def


code:
import type {CodeKeywordDefinition, SchemaObject} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {alwaysValidSchema} from "../../compile/util"
import {validateArray} from "../code"
import {_, not} from "../../compile/codegen"
import {checkMetadata} from "./metadata"
import {checkNullable} from "./nullable"
import {typeError, _JTDTypeError} from "./error"

export type JTDElementsError = _JTDTypeError<"elements", "array", SchemaObject>

const def: CodeKeywordDefinition = {
  keyword: "elements",
  schemaType: "object",
  error: typeError("array"),
  code(cxt: KeywordCxt) {
    checkMetadata(cxt)
    const {gen, data, schema, it} = cxt
    if (alwaysValidSchema(it, schema)) return
    const [valid] = checkNullable(cxt)
    gen.if(not(valid), () =>
      gen.if(
        _`Array.isArray(${data})`,
        () => gen.assign(valid, validateArray(cxt)),
        () => cxt.error()
      )
    )
    cxt.ok(valid)
  },
}

export default def


code:
import type {CodeKeywordDefinition, KeywordErrorDefinition, ErrorObject} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, or, and, Code} from "../../compile/codegen"
import {checkMetadata} from "./metadata"
import {checkNullable} from "./nullable"

export type JTDEnumError = ErrorObject<"enum", {allowedValues: string[]}, string[]>

const error: KeywordErrorDefinition = {
  message: "must be equal to one of the allowed values",
  params: ({schemaCode}) => _`{allowedValues: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "enum",
  schemaType: "array",
  error,
  code(cxt: KeywordCxt) {
    checkMetadata(cxt)
    const {gen, data, schema, schemaValue, parentSchema, it} = cxt
    if (schema.length === 0) throw new Error("enum must have non-empty array")
    if (schema.length !== new Set(schema).size) throw new Error("enum items must be unique")
    let valid: Code
    const isString = _`typeof ${data} == "string"`
    if (schema.length >= it.opts.loopEnum) {
      let cond: Code
      ;[valid, cond] = checkNullable(cxt, isString)
      gen.if(cond, loopEnum)
    } else {
      /* istanbul ignore if */
      if (!Array.isArray(schema)) throw new Error("ajv implementation error")
      valid = and(isString, or(...schema.map((value: string) => _`${data} === ${value}`)))
      if (parentSchema.nullable) valid = or(_`${data} === null`, valid)
    }
    cxt.pass(valid)

    function loopEnum(): void {
      gen.forOf("v", schemaValue as Code, (v) =>
        gen.if(_`${valid} = ${data} === ${v}`, () => gen.break())
      )
    }
  },
}

export default def


code:
import type {KeywordErrorDefinition, KeywordErrorCxt, ErrorObject} from "../../types"
import {_, Code} from "../../compile/codegen"

export type _JTDTypeError<K extends string, T extends string, S> = ErrorObject<
  K,
  {type: T; nullable: boolean},
  S
>

export function typeError(t: string): KeywordErrorDefinition {
  return {
    message: (cxt) => typeErrorMessage(cxt, t),
    params: (cxt) => typeErrorParams(cxt, t),
  }
}

export function typeErrorMessage({parentSchema}: KeywordErrorCxt, t: string): string {
  return parentSchema?.nullable ? `must be ${t} or null` : `must be ${t}`
}

export function typeErrorParams({parentSchema}: KeywordErrorCxt, t: string): Code {
  return _`{type: ${t}, nullable: ${!!parentSchema?.nullable}}`
}


code:
import type {Vocabulary} from "../../types"
import refKeyword from "./ref"
import typeKeyword, {JTDTypeError} from "./type"
import enumKeyword, {JTDEnumError} from "./enum"
import elements, {JTDElementsError} from "./elements"
import properties, {JTDPropertiesError} from "./properties"
import optionalProperties from "./optionalProperties"
import discriminator, {JTDDiscriminatorError} from "./discriminator"
import values, {JTDValuesError} from "./values"
import union from "./union"
import metadata from "./metadata"

const jtdVocabulary: Vocabulary = [
  "definitions",
  refKeyword,
  typeKeyword,
  enumKeyword,
  elements,
  properties,
  optionalProperties,
  discriminator,
  values,
  union,
  metadata,
  {keyword: "additionalProperties", schemaType: "boolean"},
  {keyword: "nullable", schemaType: "boolean"},
]

export default jtdVocabulary

export type JTDErrorObject =
  | JTDTypeError
  | JTDEnumError
  | JTDElementsError
  | JTDPropertiesError
  | JTDDiscriminatorError
  | JTDValuesError


code:
import {KeywordCxt} from "../../ajv"
import type {CodeKeywordDefinition} from "../../types"
import {alwaysValidSchema} from "../../compile/util"

const def: CodeKeywordDefinition = {
  keyword: "metadata",
  schemaType: "object",
  code(cxt: KeywordCxt) {
    checkMetadata(cxt)
    const {gen, schema, it} = cxt
    if (alwaysValidSchema(it, schema)) return
    const valid = gen.name("valid")
    cxt.subschema({keyword: "metadata", jtdMetadata: true}, valid)
    cxt.ok(valid)
  },
}

export function checkMetadata({it, keyword}: KeywordCxt, metadata?: boolean): void {
  if (it.jtdMetadata !== metadata) {
    throw new Error(`JTD: "${keyword}" cannot be used in this schema location`)
  }
}

export default def


code:
import type {KeywordCxt} from "../../compile/validate"
import {_, not, nil, Code, Name} from "../../compile/codegen"

export function checkNullable(
  {gen, data, parentSchema}: KeywordCxt,
  cond: Code = nil
): [Name, Code] {
  const valid = gen.name("valid")
  if (parentSchema.nullable) {
    gen.let(valid, _`${data} === null`)
    cond = not(valid)
  } else {
    gen.let(valid, false)
  }
  return [valid, cond]
}

export function checkNullableObject(cxt: KeywordCxt, cond: Code): [Name, Code] {
  const [valid, cond_] = checkNullable(cxt, cond)
  return [valid, _`${cond_} && typeof ${cxt.data} == "object" && !Array.isArray(${cxt.data})`]
}


code:
import type {CodeKeywordDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {validateProperties, error} from "./properties"

const def: CodeKeywordDefinition = {
  keyword: "optionalProperties",
  schemaType: "object",
  error,
  code(cxt: KeywordCxt) {
    if (cxt.parentSchema.properties) return
    validateProperties(cxt)
  },
}

export default def


code:
import type {
  CodeKeywordDefinition,
  ErrorObject,
  KeywordErrorDefinition,
  SchemaObject,
} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {propertyInData, allSchemaProperties, isOwnProperty} from "../code"
import {alwaysValidSchema, schemaRefOrVal} from "../../compile/util"
import {_, and, not, Code, Name} from "../../compile/codegen"
import {checkMetadata} from "./metadata"
import {checkNullableObject} from "./nullable"
import {typeErrorMessage, typeErrorParams, _JTDTypeError} from "./error"

enum PropError {
  Additional = "additional",
  Missing = "missing",
}

type PropKeyword = "properties" | "optionalProperties"

type PropSchema = {[P in string]?: SchemaObject}

export type JTDPropertiesError =
  | _JTDTypeError<PropKeyword, "object", PropSchema>
  | ErrorObject<PropKeyword, {error: PropError.Additional; additionalProperty: string}, PropSchema>
  | ErrorObject<PropKeyword, {error: PropError.Missing; missingProperty: string}, PropSchema>

export const error: KeywordErrorDefinition = {
  message: (cxt) => {
    const {params} = cxt
    return params.propError
      ? params.propError === PropError.Additional
        ? "must NOT have additional properties"
        : `must have property '${params.missingProperty}'`
      : typeErrorMessage(cxt, "object")
  },
  params: (cxt) => {
    const {params} = cxt
    return params.propError
      ? params.propError === PropError.Additional
        ? _`{error: ${params.propError}, additionalProperty: ${params.additionalProperty}}`
        : _`{error: ${params.propError}, missingProperty: ${params.missingProperty}}`
      : typeErrorParams(cxt, "object")
  },
}

const def: CodeKeywordDefinition = {
  keyword: "properties",
  schemaType: "object",
  error,
  code: validateProperties,
}

// const error: KeywordErrorDefinition = {
//   message: "should NOT have additional properties",
//   params: ({params}) => _`{additionalProperty: ${params.additionalProperty}}`,
// }

export function validateProperties(cxt: KeywordCxt): void {
  checkMetadata(cxt)
  const {gen, data, parentSchema, it} = cxt
  const {additionalProperties, nullable} = parentSchema
  if (it.jtdDiscriminator && nullable) throw new Error("JTD: nullable inside discriminator mapping")
  if (commonProperties()) {
    throw new Error("JTD: properties and optionalProperties have common members")
  }
  const [allProps, properties] = schemaProperties("properties")
  const [allOptProps, optProperties] = schemaProperties("optionalProperties")
  if (properties.length === 0 && optProperties.length === 0 && additionalProperties) {
    return
  }

  const [valid, cond] =
    it.jtdDiscriminator === undefined
      ? checkNullableObject(cxt, data)
      : [gen.let("valid", false), true]
  gen.if(cond, () =>
    gen.assign(valid, true).block(() => {
      validateProps(properties, "properties", true)
      validateProps(optProperties, "optionalProperties")
      if (!additionalProperties) validateAdditional()
    })
  )
  cxt.pass(valid)

  function commonProperties(): boolean {
    const props = parentSchema.properties as Record<string, any> | undefined
    const optProps = parentSchema.optionalProperties as Record<string, any> | undefined
    if (!(props && optProps)) return false
    for (const p in props) {
      if (Object.prototype.hasOwnProperty.call(optProps, p)) return true
    }
    return false
  }

  function schemaProperties(keyword: string): [string[], string[]] {
    const schema = parentSchema[keyword]
    const allPs = schema ? allSchemaProperties(schema) : []
    if (it.jtdDiscriminator && allPs.some((p) => p === it.jtdDiscriminator)) {
      throw new Error(`JTD: discriminator tag used in ${keyword}`)
    }
    const ps = allPs.filter((p) => !alwaysValidSchema(it, schema[p]))
    return [allPs, ps]
  }

  function validateProps(props: string[], keyword: string, required?: boolean): void {
    const _valid = gen.var("valid")
    for (const prop of props) {
      gen.if(
        propertyInData(gen, data, prop, it.opts.ownProperties),
        () => applyPropertySchema(prop, keyword, _valid),
        () => missingProperty(prop)
      )
      cxt.ok(_valid)
    }

    function missingProperty(prop: string): void {
      if (required) {
        gen.assign(_valid, false)
        cxt.error(false, {propError: PropError.Missing, missingProperty: prop}, {schemaPath: prop})
      } else {
        gen.assign(_valid, true)
      }
    }
  }

  function applyPropertySchema(prop: string, keyword: string, _valid: Name): void {
    cxt.subschema(
      {
        keyword,
        schemaProp: prop,
        dataProp: prop,
      },
      _valid
    )
  }

  function validateAdditional(): void {
    gen.forIn("key", data, (key: Name) => {
      const addProp = isAdditional(key, allProps, "properties", it.jtdDiscriminator)
      const addOptProp = isAdditional(key, allOptProps, "optionalProperties")
      const extra =
        addProp === true ? addOptProp : addOptProp === true ? addProp : and(addProp, addOptProp)
      gen.if(extra, () => {
        if (it.opts.removeAdditional) {
          gen.code(_`delete ${data}[${key}]`)
        } else {
          cxt.error(
            false,
            {propError: PropError.Additional, additionalProperty: key},
            {instancePath: key, parentSchema: true}
          )
          if (!it.opts.allErrors) gen.break()
        }
      })
    })
  }

  function isAdditional(
    key: Name,
    props: string[],
    keyword: string,
    jtdDiscriminator?: string
  ): Code | true {
    let additional: Code | boolean
    if (props.length > 8) {
      // TODO maybe an option instead of hard-coded 8?
      const propsSchema = schemaRefOrVal(it, parentSchema[keyword], keyword)
      additional = not(isOwnProperty(gen, propsSchema as Code, key))
      if (jtdDiscriminator !== undefined) {
        additional = and(additional, _`${key} !== ${jtdDiscriminator}`)
      }
    } else if (props.length || jtdDiscriminator !== undefined) {
      const ps = jtdDiscriminator === undefined ? props : [jtdDiscriminator].concat(props)
      additional = and(...ps.map((p) => _`${key} !== ${p}`))
    } else {
      additional = true
    }
    return additional
  }
}

export default def


code:
import type {CodeKeywordDefinition, AnySchemaObject} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {compileSchema, SchemaEnv} from "../../compile"
import {_, not, nil, stringify} from "../../compile/codegen"
import MissingRefError from "../../compile/ref_error"
import N from "../../compile/names"
import {getValidate, callRef} from "../core/ref"
import {checkMetadata} from "./metadata"

const def: CodeKeywordDefinition = {
  keyword: "ref",
  schemaType: "string",
  code(cxt: KeywordCxt) {
    checkMetadata(cxt)
    const {gen, data, schema: ref, parentSchema, it} = cxt
    const {
      schemaEnv: {root},
    } = it
    const valid = gen.name("valid")
    if (parentSchema.nullable) {
      gen.var(valid, _`${data} === null`)
      gen.if(not(valid), validateJtdRef)
    } else {
      gen.var(valid, false)
      validateJtdRef()
    }
    cxt.ok(valid)

    function validateJtdRef(): void {
      const refSchema = (root.schema as AnySchemaObject).definitions?.[ref]
      if (!refSchema) {
        throw new MissingRefError(it.opts.uriResolver, "", ref, `No definition ${ref}`)
      }
      if (hasRef(refSchema) || !it.opts.inlineRefs) callValidate(refSchema)
      else inlineRefSchema(refSchema)
    }

    function callValidate(schema: AnySchemaObject): void {
      const sch = compileSchema.call(
        it.self,
        new SchemaEnv({schema, root, schemaPath: `/definitions/${ref}`})
      )
      const v = getValidate(cxt, sch)
      const errsCount = gen.const("_errs", N.errors)
      callRef(cxt, v, sch, sch.$async)
      gen.assign(valid, _`${errsCount} === ${N.errors}`)
    }

    function inlineRefSchema(schema: AnySchemaObject): void {
      const schName = gen.scopeValue(
        "schema",
        it.opts.code.source === true ? {ref: schema, code: stringify(schema)} : {ref: schema}
      )
      cxt.subschema(
        {
          schema,
          dataTypes: [],
          schemaPath: nil,
          topSchemaRef: schName,
          errSchemaPath: `/definitions/${ref}`,
        },
        valid
      )
    }
  },
}

export function hasRef(schema: AnySchemaObject): boolean {
  for (const key in schema) {
    let sch: AnySchemaObject
    if (key === "ref" || (typeof (sch = schema[key]) == "object" && hasRef(sch))) return true
  }
  return false
}

export default def


code:
import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, nil, or, Code} from "../../compile/codegen"
import validTimestamp from "../../runtime/timestamp"
import {useFunc} from "../../compile/util"
import {checkMetadata} from "./metadata"
import {typeErrorMessage, typeErrorParams, _JTDTypeError} from "./error"

export type JTDTypeError = _JTDTypeError<"type", JTDType, JTDType>

export type IntType = "int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32"

export const intRange: {[T in IntType]: [number, number, number]} = {
  int8: [-128, 127, 3],
  uint8: [0, 255, 3],
  int16: [-32768, 32767, 5],
  uint16: [0, 65535, 5],
  int32: [-2147483648, 2147483647, 10],
  uint32: [0, 4294967295, 10],
}

export type JTDType = "boolean" | "string" | "timestamp" | "float32" | "float64" | IntType

const error: KeywordErrorDefinition = {
  message: (cxt) => typeErrorMessage(cxt, cxt.schema),
  params: (cxt) => typeErrorParams(cxt, cxt.schema),
}

function timestampCode(cxt: KeywordCxt): Code {
  const {gen, data, it} = cxt
  const {timestamp, allowDate} = it.opts
  if (timestamp === "date") return _`${data} instanceof Date `
  const vts = useFunc(gen, validTimestamp)
  const allowDateArg = allowDate ? _`, true` : nil
  const validString = _`typeof ${data} == "string" && ${vts}(${data}${allowDateArg})`
  return timestamp === "string" ? validString : or(_`${data} instanceof Date`, validString)
}

const def: CodeKeywordDefinition = {
  keyword: "type",
  schemaType: "string",
  error,
  code(cxt: KeywordCxt) {
    checkMetadata(cxt)
    const {data, schema, parentSchema, it} = cxt
    let cond: Code
    switch (schema) {
      case "boolean":
      case "string":
        cond = _`typeof ${data} == ${schema}`
        break
      case "timestamp": {
        cond = timestampCode(cxt)
        break
      }
      case "float32":
      case "float64":
        cond = _`typeof ${data} == "number"`
        break
      default: {
        const sch = schema as IntType
        cond = _`typeof ${data} == "number" && isFinite(${data}) && !(${data} % 1)`
        if (!it.opts.int32range && (sch === "int32" || sch === "uint32")) {
          if (sch === "uint32") cond = _`${cond} && ${data} >= 0`
        } else {
          const [min, max] = intRange[sch]
          cond = _`${cond} && ${data} >= ${min} && ${data} <= ${max}`
        }
      }
    }
    cxt.pass(parentSchema.nullable ? or(_`${data} === null`, cond) : cond)
  },
}

export default def


code:
import type {CodeKeywordDefinition} from "../../types"
import {validateUnion} from "../code"

const def: CodeKeywordDefinition = {
  keyword: "union",
  schemaType: "array",
  trackErrors: true,
  code: validateUnion,
  error: {message: "must match a schema in union"},
}

export default def


code:
import type {CodeKeywordDefinition, SchemaObject} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {alwaysValidSchema, Type} from "../../compile/util"
import {not, or, Name} from "../../compile/codegen"
import {checkMetadata} from "./metadata"
import {checkNullableObject} from "./nullable"
import {typeError, _JTDTypeError} from "./error"

export type JTDValuesError = _JTDTypeError<"values", "object", SchemaObject>

const def: CodeKeywordDefinition = {
  keyword: "values",
  schemaType: "object",
  error: typeError("object"),
  code(cxt: KeywordCxt) {
    checkMetadata(cxt)
    const {gen, data, schema, it} = cxt
    const [valid, cond] = checkNullableObject(cxt, data)
    if (alwaysValidSchema(it, schema)) {
      gen.if(not(or(cond, valid)), () => cxt.error())
    } else {
      gen.if(cond)
      gen.assign(valid, validateMap())
      gen.elseIf(not(valid))
      cxt.error()
      gen.endIf()
    }
    cxt.ok(valid)

    function validateMap(): Name | boolean {
      const _valid = gen.name("valid")
      if (it.allErrors) {
        const validMap = gen.let("valid", true)
        validateValues(() => gen.assign(validMap, false))
        return validMap
      }
      gen.var(_valid, true)
      validateValues(() => gen.break())
      return _valid

      function validateValues(notValid: () => void): void {
        gen.forIn("key", data, (key) => {
          cxt.subschema(
            {
              keyword: "values",
              dataProp: key,
              dataPropType: Type.Str,
            },
            _valid
          )
          gen.if(not(_valid), notValid)
        })
      }
    }
  },
}

export default def


code:
import type {Vocabulary} from "../types"

export const metadataVocabulary: Vocabulary = [
  "title",
  "description",
  "default",
  "deprecated",
  "readOnly",
  "writeOnly",
  "examples",
]

export const contentVocabulary: Vocabulary = [
  "contentMediaType",
  "contentEncoding",
  "contentSchema",
]


code:
import type {Vocabulary} from "../types"
import dependentRequired from "./validation/dependentRequired"
import dependentSchemas from "./applicator/dependentSchemas"
import limitContains from "./validation/limitContains"

const next: Vocabulary = [dependentRequired, dependentSchemas, limitContains]

export default next


code:
import type {Vocabulary} from "../../types"
import unevaluatedProperties from "./unevaluatedProperties"
import unevaluatedItems from "./unevaluatedItems"

const unevaluated: Vocabulary = [unevaluatedProperties, unevaluatedItems]

export default unevaluated


code:
import type {
  CodeKeywordDefinition,
  ErrorObject,
  KeywordErrorDefinition,
  AnySchema,
} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, str, not, Name} from "../../compile/codegen"
import {alwaysValidSchema, Type} from "../../compile/util"

export type UnevaluatedItemsError = ErrorObject<"unevaluatedItems", {limit: number}, AnySchema>

const error: KeywordErrorDefinition = {
  message: ({params: {len}}) => str`must NOT have more than ${len} items`,
  params: ({params: {len}}) => _`{limit: ${len}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "unevaluatedItems",
  type: "array",
  schemaType: ["boolean", "object"],
  error,
  code(cxt: KeywordCxt) {
    const {gen, schema, data, it} = cxt
    const items = it.items || 0
    if (items === true) return
    const len = gen.const("len", _`${data}.length`)
    if (schema === false) {
      cxt.setParams({len: items})
      cxt.fail(_`${len} > ${items}`)
    } else if (typeof schema == "object" && !alwaysValidSchema(it, schema)) {
      const valid = gen.var("valid", _`${len} <= ${items}`)
      gen.if(not(valid), () => validateItems(valid, items))
      cxt.ok(valid)
    }
    it.items = true

    function validateItems(valid: Name, from: Name | number): void {
      gen.forRange("i", from, len, (i) => {
        cxt.subschema({keyword: "unevaluatedItems", dataProp: i, dataPropType: Type.Num}, valid)
        if (!it.allErrors) gen.if(not(valid), () => gen.break())
      })
    }
  },
}

export default def


code:
import type {
  CodeKeywordDefinition,
  KeywordErrorDefinition,
  ErrorObject,
  AnySchema,
} from "../../types"
import {_, not, and, Name, Code} from "../../compile/codegen"
import {alwaysValidSchema, Type} from "../../compile/util"
import N from "../../compile/names"

export type UnevaluatedPropertiesError = ErrorObject<
  "unevaluatedProperties",
  {unevaluatedProperty: string},
  AnySchema
>

const error: KeywordErrorDefinition = {
  message: "must NOT have unevaluated properties",
  params: ({params}) => _`{unevaluatedProperty: ${params.unevaluatedProperty}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "unevaluatedProperties",
  type: "object",
  schemaType: ["boolean", "object"],
  trackErrors: true,
  error,
  code(cxt) {
    const {gen, schema, data, errsCount, it} = cxt
    /* istanbul ignore if */
    if (!errsCount) throw new Error("ajv implementation error")
    const {allErrors, props} = it
    if (props instanceof Name) {
      gen.if(_`${props} !== true`, () =>
        gen.forIn("key", data, (key: Name) =>
          gen.if(unevaluatedDynamic(props, key), () => unevaluatedPropCode(key))
        )
      )
    } else if (props !== true) {
      gen.forIn("key", data, (key: Name) =>
        props === undefined
          ? unevaluatedPropCode(key)
          : gen.if(unevaluatedStatic(props, key), () => unevaluatedPropCode(key))
      )
    }
    it.props = true
    cxt.ok(_`${errsCount} === ${N.errors}`)

    function unevaluatedPropCode(key: Name): void {
      if (schema === false) {
        cxt.setParams({unevaluatedProperty: key})
        cxt.error()
        if (!allErrors) gen.break()
        return
      }

      if (!alwaysValidSchema(it, schema)) {
        const valid = gen.name("valid")
        cxt.subschema(
          {
            keyword: "unevaluatedProperties",
            dataProp: key,
            dataPropType: Type.Str,
          },
          valid
        )
        if (!allErrors) gen.if(not(valid), () => gen.break())
      }
    }

    function unevaluatedDynamic(evaluatedProps: Name, key: Name): Code {
      return _`!${evaluatedProps} || !${evaluatedProps}[${key}]`
    }

    function unevaluatedStatic(evaluatedProps: {[K in string]?: true}, key: Name): Code {
      const ps: Code[] = []
      for (const p in evaluatedProps) {
        if (evaluatedProps[p] === true) ps.push(_`${key} !== ${p}`)
      }
      return and(...ps)
    }
  },
}

export default def


code:
import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_} from "../../compile/codegen"
import {useFunc} from "../../compile/util"
import equal from "../../runtime/equal"

export type ConstError = ErrorObject<"const", {allowedValue: any}>

const error: KeywordErrorDefinition = {
  message: "must be equal to constant",
  params: ({schemaCode}) => _`{allowedValue: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "const",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {gen, data, $data, schemaCode, schema} = cxt
    if ($data || (schema && typeof schema == "object")) {
      cxt.fail$data(_`!${useFunc(gen, equal)}(${data}, ${schemaCode})`)
    } else {
      cxt.fail(_`${schema} !== ${data}`)
    }
  },
}

export default def


code:
import type {CodeKeywordDefinition, ErrorObject} from "../../types"
import {
  validatePropertyDeps,
  error,
  DependenciesErrorParams,
  PropertyDependencies,
} from "../applicator/dependencies"

export type DependentRequiredError = ErrorObject<
  "dependentRequired",
  DependenciesErrorParams,
  PropertyDependencies
>

const def: CodeKeywordDefinition = {
  keyword: "dependentRequired",
  type: "object",
  schemaType: "object",
  error,
  code: (cxt) => validatePropertyDeps(cxt),
}

export default def


code:
import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, or, Name, Code} from "../../compile/codegen"
import {useFunc} from "../../compile/util"
import equal from "../../runtime/equal"

export type EnumError = ErrorObject<"enum", {allowedValues: any[]}, any[] | {$data: string}>

const error: KeywordErrorDefinition = {
  message: "must be equal to one of the allowed values",
  params: ({schemaCode}) => _`{allowedValues: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "enum",
  schemaType: "array",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {gen, data, $data, schema, schemaCode, it} = cxt
    if (!$data && schema.length === 0) throw new Error("enum must have non-empty array")
    const useLoop = schema.length >= it.opts.loopEnum
    let eql: Name | undefined
    const getEql = (): Name => (eql ??= useFunc(gen, equal))

    let valid: Code
    if (useLoop || $data) {
      valid = gen.let("valid")
      cxt.block$data(valid, loopEnum)
    } else {
      /* istanbul ignore if */
      if (!Array.isArray(schema)) throw new Error("ajv implementation error")
      const vSchema = gen.const("vSchema", schemaCode)
      valid = or(...schema.map((_x: unknown, i: number) => equalCode(vSchema, i)))
    }
    cxt.pass(valid)

    function loopEnum(): void {
      gen.assign(valid, false)
      gen.forOf("v", schemaCode as Code, (v) =>
        gen.if(_`${getEql()}(${data}, ${v})`, () => gen.assign(valid, true).break())
      )
    }

    function equalCode(vSchema: Name, i: number): Code {
      const sch = schema[i]
      return typeof sch === "object" && sch !== null
        ? _`${getEql()}(${data}, ${vSchema}[${i}])`
        : _`${data} === ${sch}`
    }
  },
}

export default def


code:
import type {ErrorObject, Vocabulary} from "../../types"
import limitNumber, {LimitNumberError} from "./limitNumber"
import multipleOf, {MultipleOfError} from "./multipleOf"
import limitLength from "./limitLength"
import pattern, {PatternError} from "./pattern"
import limitProperties from "./limitProperties"
import required, {RequiredError} from "./required"
import limitItems from "./limitItems"
import uniqueItems, {UniqueItemsError} from "./uniqueItems"
import constKeyword, {ConstError} from "./const"
import enumKeyword, {EnumError} from "./enum"

const validation: Vocabulary = [
  // number
  limitNumber,
  multipleOf,
  // string
  limitLength,
  pattern,
  // object
  limitProperties,
  required,
  // array
  limitItems,
  uniqueItems,
  // any
  {keyword: "type", schemaType: ["string", "array"]},
  {keyword: "nullable", schemaType: "boolean"},
  constKeyword,
  enumKeyword,
]

export default validation

type LimitError = ErrorObject<
  "maxItems" | "minItems" | "minProperties" | "maxProperties" | "minLength" | "maxLength",
  {limit: number},
  number | {$data: string}
>

export type ValidationKeywordError =
  | LimitError
  | LimitNumberError
  | MultipleOfError
  | PatternError
  | RequiredError
  | UniqueItemsError
  | ConstError
  | EnumError


code:
import type {CodeKeywordDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {checkStrictMode} from "../../compile/util"

const def: CodeKeywordDefinition = {
  keyword: ["maxContains", "minContains"],
  type: "array",
  schemaType: "number",
  code({keyword, parentSchema, it}: KeywordCxt) {
    if (parentSchema.contains === undefined) {
      checkStrictMode(it, `"${keyword}" without "contains" is ignored`)
    }
  },
}

export default def


code:
import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, str, operators} from "../../compile/codegen"

const error: KeywordErrorDefinition = {
  message({keyword, schemaCode}) {
    const comp = keyword === "maxItems" ? "more" : "fewer"
    return str`must NOT have ${comp} than ${schemaCode} items`
  },
  params: ({schemaCode}) => _`{limit: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: ["maxItems", "minItems"],
  type: "array",
  schemaType: "number",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {keyword, data, schemaCode} = cxt
    const op = keyword === "maxItems" ? operators.GT : operators.LT
    cxt.fail$data(_`${data}.length ${op} ${schemaCode}`)
  },
}

export default def


code:
import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, str, operators} from "../../compile/codegen"
import {useFunc} from "../../compile/util"
import ucs2length from "../../runtime/ucs2length"

const error: KeywordErrorDefinition = {
  message({keyword, schemaCode}) {
    const comp = keyword === "maxLength" ? "more" : "fewer"
    return str`must NOT have ${comp} than ${schemaCode} characters`
  },
  params: ({schemaCode}) => _`{limit: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: ["maxLength", "minLength"],
  type: "string",
  schemaType: "number",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {keyword, data, schemaCode, it} = cxt
    const op = keyword === "maxLength" ? operators.GT : operators.LT
    const len =
      it.opts.unicode === false ? _`${data}.length` : _`${useFunc(cxt.gen, ucs2length)}(${data})`
    cxt.fail$data(_`${len} ${op} ${schemaCode}`)
  },
}

export default def


code:
import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, str, operators, Code} from "../../compile/codegen"

const ops = operators

type Kwd = "maximum" | "minimum" | "exclusiveMaximum" | "exclusiveMinimum"

type Comparison = "<=" | ">=" | "<" | ">"

const KWDs: {[K in Kwd]: {okStr: Comparison; ok: Code; fail: Code}} = {
  maximum: {okStr: "<=", ok: ops.LTE, fail: ops.GT},
  minimum: {okStr: ">=", ok: ops.GTE, fail: ops.LT},
  exclusiveMaximum: {okStr: "<", ok: ops.LT, fail: ops.GTE},
  exclusiveMinimum: {okStr: ">", ok: ops.GT, fail: ops.LTE},
}

export type LimitNumberError = ErrorObject<
  Kwd,
  {limit: number; comparison: Comparison},
  number | {$data: string}
>

const error: KeywordErrorDefinition = {
  message: ({keyword, schemaCode}) => str`must be ${KWDs[keyword as Kwd].okStr} ${schemaCode}`,
  params: ({keyword, schemaCode}) =>
    _`{comparison: ${KWDs[keyword as Kwd].okStr}, limit: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: Object.keys(KWDs),
  type: "number",
  schemaType: "number",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {keyword, data, schemaCode} = cxt
    cxt.fail$data(_`${data} ${KWDs[keyword as Kwd].fail} ${schemaCode} || isNaN(${data})`)
  },
}

export default def


code:
import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, str, operators} from "../../compile/codegen"

const error: KeywordErrorDefinition = {
  message({keyword, schemaCode}) {
    const comp = keyword === "maxProperties" ? "more" : "fewer"
    return str`must NOT have ${comp} than ${schemaCode} properties`
  },
  params: ({schemaCode}) => _`{limit: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: ["maxProperties", "minProperties"],
  type: "object",
  schemaType: "number",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {keyword, data, schemaCode} = cxt
    const op = keyword === "maxProperties" ? operators.GT : operators.LT
    cxt.fail$data(_`Object.keys(${data}).length ${op} ${schemaCode}`)
  },
}

export default def


code:
import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, str} from "../../compile/codegen"

export type MultipleOfError = ErrorObject<
  "multipleOf",
  {multipleOf: number},
  number | {$data: string}
>

const error: KeywordErrorDefinition = {
  message: ({schemaCode}) => str`must be multiple of ${schemaCode}`,
  params: ({schemaCode}) => _`{multipleOf: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "multipleOf",
  type: "number",
  schemaType: "number",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {gen, data, schemaCode, it} = cxt
    // const bdt = bad$DataType(schemaCode, <string>def.schemaType, $data)
    const prec = it.opts.multipleOfPrecision
    const res = gen.let("res")
    const invalid = prec
      ? _`Math.abs(Math.round(${res}) - ${res}) > 1e-${prec}`
      : _`${res} !== parseInt(${res})`
    cxt.fail$data(_`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid}))`)
  },
}

export default def


code:
import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {usePattern} from "../code"
import {_, str} from "../../compile/codegen"

export type PatternError = ErrorObject<"pattern", {pattern: string}, string | {$data: string}>

const error: KeywordErrorDefinition = {
  message: ({schemaCode}) => str`must match pattern "${schemaCode}"`,
  params: ({schemaCode}) => _`{pattern: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "pattern",
  type: "string",
  schemaType: "string",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {data, $data, schema, schemaCode, it} = cxt
    // TODO regexp should be wrapped in try/catchs
    const u = it.opts.unicodeRegExp ? "u" : ""
    const regExp = $data ? _`(new RegExp(${schemaCode}, ${u}))` : usePattern(cxt, schema)
    cxt.fail$data(_`!${regExp}.test(${data})`)
  },
}

export default def


code:
import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {
  checkReportMissingProp,
  checkMissingProp,
  reportMissingProp,
  propertyInData,
  noPropertyInData,
} from "../code"
import {_, str, nil, not, Name, Code} from "../../compile/codegen"
import {checkStrictMode} from "../../compile/util"

export type RequiredError = ErrorObject<
  "required",
  {missingProperty: string},
  string[] | {$data: string}
>

const error: KeywordErrorDefinition = {
  message: ({params: {missingProperty}}) => str`must have required property '${missingProperty}'`,
  params: ({params: {missingProperty}}) => _`{missingProperty: ${missingProperty}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "required",
  type: "object",
  schemaType: "array",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {gen, schema, schemaCode, data, $data, it} = cxt
    const {opts} = it
    if (!$data && schema.length === 0) return
    const useLoop = schema.length >= opts.loopRequired
    if (it.allErrors) allErrorsMode()
    else exitOnErrorMode()

    if (opts.strictRequired) {
      const props = cxt.parentSchema.properties
      const {definedProperties} = cxt.it
      for (const requiredKey of schema) {
        if (props?.[requiredKey] === undefined && !definedProperties.has(requiredKey)) {
          const schemaPath = it.schemaEnv.baseId + it.errSchemaPath
          const msg = `required property "${requiredKey}" is not defined at "${schemaPath}" (strictRequired)`
          checkStrictMode(it, msg, it.opts.strictRequired)
        }
      }
    }

    function allErrorsMode(): void {
      if (useLoop || $data) {
        cxt.block$data(nil, loopAllRequired)
      } else {
        for (const prop of schema) {
          checkReportMissingProp(cxt, prop)
        }
      }
    }

    function exitOnErrorMode(): void {
      const missing = gen.let("missing")
      if (useLoop || $data) {
        const valid = gen.let("valid", true)
        cxt.block$data(valid, () => loopUntilMissing(missing, valid))
        cxt.ok(valid)
      } else {
        gen.if(checkMissingProp(cxt, schema, missing))
        reportMissingProp(cxt, missing)
        gen.else()
      }
    }

    function loopAllRequired(): void {
      gen.forOf("prop", schemaCode as Code, (prop) => {
        cxt.setParams({missingProperty: prop})
        gen.if(noPropertyInData(gen, data, prop, opts.ownProperties), () => cxt.error())
      })
    }

    function loopUntilMissing(missing: Name, valid: Name): void {
      cxt.setParams({missingProperty: missing})
      gen.forOf(
        missing,
        schemaCode as Code,
        () => {
          gen.assign(valid, propertyInData(gen, data, missing, opts.ownProperties))
          gen.if(not(valid), () => {
            cxt.error()
            gen.break()
          })
        },
        nil
      )
    }
  },
}

export default def


code:
import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {checkDataTypes, getSchemaTypes, DataType} from "../../compile/validate/dataType"
import {_, str, Name} from "../../compile/codegen"
import {useFunc} from "../../compile/util"
import equal from "../../runtime/equal"

export type UniqueItemsError = ErrorObject<
  "uniqueItems",
  {i: number; j: number},
  boolean | {$data: string}
>

const error: KeywordErrorDefinition = {
  message: ({params: {i, j}}) =>
    str`must NOT have duplicate items (items ## ${j} and ${i} are identical)`,
  params: ({params: {i, j}}) => _`{i: ${i}, j: ${j}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "uniqueItems",
  type: "array",
  schemaType: "boolean",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {gen, data, $data, schema, parentSchema, schemaCode, it} = cxt
    if (!$data && !schema) return
    const valid = gen.let("valid")
    const itemTypes = parentSchema.items ? getSchemaTypes(parentSchema.items) : []
    cxt.block$data(valid, validateUniqueItems, _`${schemaCode} === false`)
    cxt.ok(valid)

    function validateUniqueItems(): void {
      const i = gen.let("i", _`${data}.length`)
      const j = gen.let("j")
      cxt.setParams({i, j})
      gen.assign(valid, true)
      gen.if(_`${i} > 1`, () => (canOptimize() ? loopN : loopN2)(i, j))
    }

    function canOptimize(): boolean {
      return itemTypes.length > 0 && !itemTypes.some((t) => t === "object" || t === "array")
    }

    function loopN(i: Name, j: Name): void {
      const item = gen.name("item")
      const wrongType = checkDataTypes(itemTypes, item, it.opts.strictNumbers, DataType.Wrong)
      const indices = gen.const("indices", _`{}`)
      gen.for(_`;${i}--;`, () => {
        gen.let(item, _`${data}[${i}]`)
        gen.if(wrongType, _`continue`)
        if (itemTypes.length > 1) gen.if(_`typeof ${item} == "string"`, _`${item} += "_"`)
        gen
          .if(_`typeof ${indices}[${item}] == "number"`, () => {
            gen.assign(j, _`${indices}[${item}]`)
            cxt.error()
            gen.assign(valid, false).break()
          })
          .code(_`${indices}[${item}] = ${i}`)
      })
    }

    function loopN2(i: Name, j: Name): void {
      const eql = useFunc(gen, equal)
      const outer = gen.name("outer")
      gen.label(outer).for(_`;${i}--;`, () =>
        gen.for(_`${j} = ${i}; ${j}--;`, () =>
          gen.if(_`${eql}(${data}[${i}], ${data}[${j}])`, () => {
            cxt.error()
            gen.assign(valid, false).break(outer)
          })
        )
      )
    }
  },
}

export default def


ajv

description: Another JSON Schema Validator

files: lib/ dist/ .runkit_example.js

eslint: eslint "lib/**/*.ts" "spec/**/*.*s" --ignore-pattern spec/JSON-Schema-Test-Suite

test-spec: cross-env TS_NODE_PROJECT=spec/tsconfig.json mocha -r ts-node/register "spec/**/*.spec.{ts,js}" -R dot

test-codegen: nyc cross-env TS_NODE_PROJECT=spec/tsconfig.json mocha -r ts-node/register 'spec/codegen.spec.ts' -R spec

test-debug: npm run test-spec -- --inspect-brk

test-cov: nyc npm run test-spec

rollup: rm -rf bundle && rollup -c

bundle: rm -rf bundle && node ./scripts/bundle.js ajv ajv7 ajv7 && node ./scripts/bundle.js 2019 ajv2019 ajv2019 && node ./scripts/bundle.js 2020 ajv2020 ajv2020 && node ./scripts/bundle.js jtd ajvJTD ajvJTD

build: rm -rf dist && tsc && cp -r lib/refs dist && rm dist/refs/json-schema-2019-09/index.ts && rm dist/refs/json-schema-2020-12/index.ts && rm dist/refs/jtd-schema.ts

json-tests: rm -rf spec/_json/*.js && node scripts/jsontests

test-karma: karma start

test-browser: rm -rf .browser && npm run bundle && scripts/prepare-tests && karma start

test-all: npm run test-cov && if-node-version 12 npm run test-browser

test: npm run json-tests && npm run prettier:check && npm run eslint && npm link && npm link --legacy-peer-deps ajv && npm run test-cov

test-ci: AJV_FULL_TEST=true npm test

prepublish: npm run build

benchmark: npm i && npm run build && npm link && cd ./benchmark && npm link --legacy-peer-deps ajv && npm i && node ./jtd

docs:dev: ./scripts/prepare-site && vuepress dev docs

docs:build: ./scripts/prepare-site && vuepress build docs

reporter: lcov text-summary

repository: ajv-validator/ajv

keywords: JSON schema validator validation jsonschema json-schema json-schema-validator json-schema-validation

author: Evgeny Poberezkin

license: MIT

type: opencollective

type: github

pre-commit: lint-staged && npm test

*.{json,yaml,js,ts}: prettier --write


code:
/**
Check if [`argv`](https://nodejs.org/docs/latest/api/process.html#process_process_argv) has a specific flag.

@param flag - CLI flag to look for. The `--` prefix is optional.
@param argv - CLI arguments. Default: `process.argv`.
@returns Whether the flag exists.

@example
```
// $ ts-node foo.ts -f --unicorn --foo=bar -- --rainbow

// foo.ts
import hasFlag = require('has-flag');

hasFlag('unicorn');
//=> true

hasFlag('--unicorn');
//=> true

hasFlag('f');
//=> true

hasFlag('-f');
//=> true

hasFlag('foo=bar');
//=> true

hasFlag('foo');
//=> false

hasFlag('rainbow');
//=> false
```
*/
declare function hasFlag(flag: string, argv?: string[]): boolean;

export = hasFlag;


code:
'use strict';

module.exports = (flag, argv = process.argv) => {
	const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--');
	const position = argv.indexOf(prefix + flag);
	const terminatorPosition = argv.indexOf('--');
	return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
};


has-flag

description: Check if argv has a specific flag

repository: sindresorhus/has-flag

test: xo && ava && tsd

keywords: has check detect contains find flag cli command-line argv process arg args argument arguments getopt minimist optimist


code:
# has-flag [![Build Status](https://travis-ci.org/sindresorhus/has-flag.svg?branch=master)](https://travis-ci.org/sindresorhus/has-flag)

> Check if [`argv`](https://nodejs.org/docs/latest/api/process.html#process_process_argv) has a specific flag

Correctly stops looking after an `--` argument terminator.

---

		Get professional support for this package with a Tidelift subscription

		Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies.

---

## Install

```
$ npm install has-flag
```

## Usage

```js
// foo.js
const hasFlag = require('has-flag');

hasFlag('unicorn');
//=> true

hasFlag('--unicorn');
//=> true

hasFlag('f');
//=> true

hasFlag('-f');
//=> true

hasFlag('foo=bar');
//=> true

hasFlag('foo');
//=> false

hasFlag('rainbow');
//=> false
```

```
$ node foo.js -f --unicorn --foo=bar -- --rainbow
```

## API

### hasFlag(flag, [argv])

Returns a boolean for whether the flag exists.

#### flag

Type: `string`

CLI flag to look for. The `--` prefix is optional.

#### argv

Type: `string[]`

Default: `process.argv`

CLI arguments.

## Security

To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.

## License

MIT © [Sindre Sorhus](https://sindresorhus.com)


code:
# jest-worker

Module for executing heavy tasks under forked processes in parallel, by providing a `Promise` based interface, minimum overhead, and bound workers.

The module works by providing an absolute path of the module to be loaded in all forked processes. All methods are exposed on the parent process as promises, so they can be `await`'ed. Child (worker) methods can either be synchronous or asynchronous.

The module also implements support for bound workers. Binding a worker means that, based on certain parameters, the same task will always be executed by the same worker. The way bound workers work is by using the returned string of the `computeWorkerKey` method. If the string was used before for a task, the call will be queued to the related worker that processed the task earlier; if not, it will be executed by the first available worker, then sticked to the worker that executed it; so the next time it will be processed by the same worker. If you have no preference on the worker executing the task, but you have defined a `computeWorkerKey` method because you want _some_ of the tasks to be sticked, you can return `null` from it.

The list of exposed methods can be explicitly provided via the `exposedMethods` option. If it is not provided, it will be obtained by requiring the child module into the main process, and analyzed via reflection. Check the "minimal example" section for a valid one.

## Install

```sh
yarn add jest-worker
```

## Example

This example covers the minimal usage:

### File `parent.js`

```js
import {Worker as JestWorker} from 'jest-worker';

async function main() {
  const worker = new JestWorker(require.resolve('./worker'));
  const result = await worker.hello('Alice'); // "Hello, Alice"
}

main();
```

### File `worker.js`

```js
export function hello(param) {
  return `Hello, ${param}`;
}
```

## Experimental worker

Node shipped with [`worker_threads`](https://nodejs.org/api/worker_threads.html), a "threading API" that uses `SharedArrayBuffers` to communicate between the main process and its child threads. This feature can significantly improve the communication time between parent and child processes in `jest-worker`.

To use `worker_threads` instead of default `child_process` you have to pass `enableWorkerThreads: true` when instantiating the worker.

## API

The `Worker` export is a constructor that is initialized by passing the worker path, plus an options object.

### `workerPath: string` (required)

Node module name or absolute path of the file to be loaded in the child processes. Use `require.resolve` to transform a relative path into an absolute one.

### `options: Object` (optional)

#### `computeWorkerKey: (method: string, ...args: Array<unknown>) => string | null` (optional)

Every time a method exposed via the API is called, `computeWorkerKey` is also called in order to bound the call to a worker. This is useful for workers that are able to cache the result or part of it. You bound calls to a worker by making `computeWorkerKey` return the same identifier for all different calls. If you do not want to bind the call to any worker, return `null`.

The callback you provide is called with the method name, plus all the rest of the arguments of the call. Thus, you have full control to decide what to return. Check a practical example on bound workers under the "bound worker usage" section.

By default, no process is bound to any worker.

#### `enableWorkerThreads: boolean` (optional)

By default, `jest-worker` will use `child_process` threads to spawn new Node.js processes. If you prefer [`worker_threads`](https://nodejs.org/api/worker_threads.html) instead, pass `enableWorkerThreads: true`.

#### `exposedMethods: ReadonlyArray<string>` (optional)

List of method names that can be called on the child processes from the parent process. You cannot expose any method named like a public `Worker` method, or starting with `_`. If you use method auto-discovery, then these methods will not be exposed, even if they exist.

#### `forkOptions: ForkOptions` (optional)

Allow customizing all options passed to `child_process.fork`. By default, some values are set (`cwd`, `env`, `execArgv` and `serialization`), but you can override them and customize the rest. For a list of valid values, check [the Node documentation](https://nodejs.org/api/child_process.html#child_process_child_process_fork_modulepath_args_options).

#### `maxRetries: number` (optional)

Maximum amount of times that a dead child can be re-spawned, per call. Defaults to `3`, pass `Infinity` to allow endless retries.

#### `numWorkers: number` (optional)

Amount of workers to spawn. Defaults to the number of CPUs minus 1.

#### `resourceLimits: ResourceLimits` (optional)

The `resourceLimits` option which will be passed to `worker_threads` workers.

#### `setupArgs: Array<unknown>` (optional)

The arguments that will be passed to the `setup` method during initialization.

#### `taskQueue: TaskQueue` (optional)

The task queue defines in which order tasks (method calls) are processed by the workers. `jest-worker` ships with a `FifoQueue` and `PriorityQueue`:

- `FifoQueue` (default): Processes the method calls (tasks) in the call order.
- `PriorityQueue`: Processes the method calls by a computed priority in natural ordering (lower priorities first). Tasks with the same priority are processed in any order (FIFO not guaranteed). The constructor accepts a single argument, the function that is passed the name of the called function and the arguments and returns a numerical value for the priority: `new require('jest-worker').PriorityQueue((method, filename) => filename.length)`.

#### `WorkerPool: new (workerPath: string, options?: WorkerPoolOptions) => WorkerPoolInterface` (optional)

Provide a custom WorkerPool class to be used for spawning child processes.

#### `workerSchedulingPolicy: 'round-robin' | 'in-order'` (optional)

Specifies the policy how tasks are assigned to workers if multiple workers are _idle_:

- `round-robin` (default): The task will be sequentially distributed onto the workers. The first task is assigned to the worker 1, the second to the worker 2, to ensure that the work is distributed across workers.
- `in-order`: The task will be assigned to the first free worker starting with worker 1 and only assign the work to worker 2 if the worker 1 is busy.

Tasks are always assigned to the first free worker as soon as tasks start to queue up. The scheduling policy does not define the task scheduling which is always first-in, first-out.

## JestWorker

### Methods

The returned `JestWorker` instance has all the exposed methods, plus some additional ones to interact with the workers itself:

#### `getStdout(): Readable`

Returns a `ReadableStream` where the standard output of all workers is piped. Note that the `silent` option of the child workers must be set to `true` to make it work. This is the default set by `jest-worker`, but keep it in mind when overriding options through `forkOptions`.

#### `getStderr(): Readable`

Returns a `ReadableStream` where the standard error of all workers is piped. Note that the `silent` option of the child workers must be set to `true` to make it work. This is the default set by `jest-worker`, but keep it in mind when overriding options through `forkOptions`.

#### `end()`

Finishes the workers by killing all workers. No further calls can be done to the `Worker` instance.

Returns a Promise that resolves with `{ forceExited: boolean }` once all workers are dead. If `forceExited` is `true`, at least one of the workers did not exit gracefully, which likely happened because it executed a leaky task that left handles open. This should be avoided, force exiting workers is a last resort to prevent creating lots of orphans.

**Note:**

`await`ing the `end()` Promise immediately after the workers are no longer needed before proceeding to do other useful things in your program may not be a good idea. If workers have to be force exited, `jest-worker` may go through multiple stages of force exiting (e.g. SIGTERM, later SIGKILL) and give the worker overall around 1 second time to exit on its own. During this time, your program will wait, even though it may not be necessary that all workers are dead before continuing execution.

Consider deliberately leaving this Promise floating (unhandled resolution). After your program has done the rest of its work and is about to exit, the Node process will wait for the Promise to resolve after all workers are dead as the last event loop task. That way you parallelized computation time of your program and waiting time and you didn't delay the outputs of your program unnecessarily.

### Worker IDs

Each worker has a unique id (index that starts with `'1'`), which is available inside the worker as `process.env.JEST_WORKER_ID`.

## Setting up and tearing down the child process

The child process can define two special methods (both of them can be asynchronous):

- `setup()`: If defined, it's executed before the first call to any method in the child.
- `teardown()`: If defined, it's executed when the farm ends.

# More examples

## Standard usage

This example covers the standard usage:

### File `parent.js`

```js
import {Worker as JestWorker} from 'jest-worker';

async function main() {
  const myWorker = new JestWorker(require.resolve('./worker'), {
    exposedMethods: ['foo', 'bar', 'getWorkerId'],
    numWorkers: 4,
  });

  console.log(await myWorker.foo('Alice')); // "Hello from foo: Alice"
  console.log(await myWorker.bar('Bob')); // "Hello from bar: Bob"
  console.log(await myWorker.getWorkerId()); // "3" -> this message has sent from the 3rd worker

  const {forceExited} = await myWorker.end();
  if (forceExited) {
    console.error('Workers failed to exit gracefully');
  }
}

main();
```

### File `worker.js`

```js
export function foo(param) {
  return `Hello from foo: ${param}`;
}

export function bar(param) {
  return `Hello from bar: ${param}`;
}

export function getWorkerId() {
  return process.env.JEST_WORKER_ID;
}
```

## Bound worker usage:

This example covers the usage with a `computeWorkerKey` method:

### File `parent.js`

```js
import {Worker as JestWorker} from 'jest-worker';

async function main() {
  const myWorker = new JestWorker(require.resolve('./worker'), {
    computeWorkerKey: (method, filename) => filename,
  });

  // Transform the given file, within the first available worker.
  console.log(await myWorker.transform('/tmp/foo.js'));

  // Wait a bit.
  await sleep(10000);

  // Transform the same file again. Will immediately return because the
  // transformed file is cached in the worker, and `computeWorkerKey` ensures
  // the same worker that processed the file the first time will process it now.
  console.log(await myWorker.transform('/tmp/foo.js'));

  const {forceExited} = await myWorker.end();
  if (forceExited) {
    console.error('Workers failed to exit gracefully');
  }
}

main();
```

### File `worker.js`

```js
import babel from '@babel/core';

const cache = Object.create(null);

export function transform(filename) {
  if (cache[filename]) {
    return cache[filename];
  }

  // jest-worker can handle both immediate results and thenables. If a
  // thenable is returned, it will be await'ed until it resolves.
  return babel.transformFileAsync(filename).then(result => {
    cache[filename] = result;

    return result;
  });
}
```


code:
'use strict';

Object.defineProperty(exports, '__esModule', {
  value: true
});
exports.default = void 0;

var _FifoQueue = _interopRequireDefault(require('./FifoQueue'));

var _types = require('./types');

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : {default: obj};
}

/**
 * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
class Farm {
  _computeWorkerKey;
  _workerSchedulingPolicy;
  _cacheKeys = Object.create(null);
  _locks = [];
  _offset = 0;
  _taskQueue;

  constructor(_numOfWorkers, _callback, options = {}) {
    var _options$workerSchedu, _options$taskQueue;

    this._numOfWorkers = _numOfWorkers;
    this._callback = _callback;
    this._computeWorkerKey = options.computeWorkerKey;
    this._workerSchedulingPolicy =
      (_options$workerSchedu = options.workerSchedulingPolicy) !== null &&
      _options$workerSchedu !== void 0
        ? _options$workerSchedu
        : 'round-robin';
    this._taskQueue =
      (_options$taskQueue = options.taskQueue) !== null &&
      _options$taskQueue !== void 0
        ? _options$taskQueue
        : new _FifoQueue.default();
  }

  doWork(method, ...args) {
    const customMessageListeners = new Set();

    const addCustomMessageListener = listener => {
      customMessageListeners.add(listener);
      return () => {
        customMessageListeners.delete(listener);
      };
    };

    const onCustomMessage = message => {
      customMessageListeners.forEach(listener => listener(message));
    };

    const promise = new Promise( // Bind args to this function so it won't reference to the parent scope.
      // This prevents a memory leak in v8, because otherwise the function will
      // retain args for the closure.
      ((args, resolve, reject) => {
        const computeWorkerKey = this._computeWorkerKey;
        const request = [_types.CHILD_MESSAGE_CALL, false, method, args];
        let worker = null;
        let hash = null;

        if (computeWorkerKey) {
          hash = computeWorkerKey.call(this, method, ...args);
          worker = hash == null ? null : this._cacheKeys[hash];
        }

        const onStart = worker => {
          if (hash != null) {
            this._cacheKeys[hash] = worker;
          }
        };

        const onEnd = (error, result) => {
          customMessageListeners.clear();

          if (error) {
            reject(error);
          } else {
            resolve(result);
          }
        };

        const task = {
          onCustomMessage,
          onEnd,
          onStart,
          request
        };

        if (worker) {
          this._taskQueue.enqueue(task, worker.getWorkerId());

          this._process(worker.getWorkerId());
        } else {
          this._push(task);
        }
      }).bind(null, args)
    );
    promise.UNSTABLE_onCustomMessage = addCustomMessageListener;
    return promise;
  }

  _process(workerId) {
    if (this._isLocked(workerId)) {
      return this;
    }

    const task = this._taskQueue.dequeue(workerId);

    if (!task) {
      return this;
    }

    if (task.request[1]) {
      throw new Error('Queue implementation returned processed task');
    } // Reference the task object outside so it won't be retained by onEnd,
    // and other properties of the task object, such as task.request can be
    // garbage collected.

    let taskOnEnd = task.onEnd;

    const onEnd = (error, result) => {
      if (taskOnEnd) {
        taskOnEnd(error, result);
      }

      taskOnEnd = null;

      this._unlock(workerId);

      this._process(workerId);
    };

    task.request[1] = true;

    this._lock(workerId);

    this._callback(
      workerId,
      task.request,
      task.onStart,
      onEnd,
      task.onCustomMessage
    );

    return this;
  }

  _push(task) {
    this._taskQueue.enqueue(task);

    const offset = this._getNextWorkerOffset();

    for (let i = 0; i < this._numOfWorkers; i++) {
      this._process((offset + i) % this._numOfWorkers);

      if (task.request[1]) {
        break;
      }
    }

    return this;
  }

  _getNextWorkerOffset() {
    switch (this._workerSchedulingPolicy) {
      case 'in-order':
        return 0;

      case 'round-robin':
        return this._offset++;
    }
  }

  _lock(workerId) {
    this._locks[workerId] = true;
  }

  _unlock(workerId) {
    this._locks[workerId] = false;
  }

  _isLocked(workerId) {
    return this._locks[workerId];
  }
}

exports.default = Farm;