/ src / builder.js
builder.js
   1  import * as API from './api.js'
   2  import * as Syntax from './syntax.js'
   3  import * as Task from './task.js'
   4  import { $, _ } from './$.js'
   5  import * as Variable from './variable.js'
   6  import { Callable } from './syntax/callable.js'
   7  import * as Selector from './selector.js'
   8  import * as Link from './data/link.js'
   9  import { toDebugString } from './debug.js'
  10  import * as JSON from './json.js'
  11  import { base58btc } from 'multiformats/bases/base58'
  12  
  13  /**
  14   * @param {unknown} descriptor
  15   * @returns {API.Type|undefined}
  16   */
  17  const fromConstructor = (descriptor) => {
  18    switch (descriptor) {
  19      case globalThis.Boolean:
  20        return { Boolean: {} }
  21      case globalThis.String:
  22        return { String: {} }
  23      case globalThis.Symbol:
  24        return { Name: {} }
  25      case globalThis.Number:
  26        return { Integer: {} }
  27      case globalThis.BigInt:
  28        return { Integer: {} }
  29      case globalThis.Uint8Array:
  30        return { Bytes: {} }
  31      case globalThis.Object:
  32        return { Entity: {} }
  33      case null:
  34        return { Null: {} }
  35    }
  36  }
  37  
  38  /**
  39   * @param {Record<string, unknown>|null|undefined} descriptor
  40   * @returns {API.Type|undefined}
  41   */
  42  const fromDescriptor = (descriptor) => {
  43    if (descriptor?.Null) {
  44      return { Null: {} }
  45    } else if (descriptor?.Boolean) {
  46      return { Boolean: {} }
  47    } else if (descriptor?.String) {
  48      return { String: {} }
  49    } else if (descriptor?.Integer) {
  50      return { Integer: {} }
  51    } else if (descriptor?.Float) {
  52      return { Float: {} }
  53    } else if (descriptor?.Bytes) {
  54      return { Bytes: {} }
  55    } else if (descriptor?.Entity) {
  56      return { Entity: {} }
  57    } else if (descriptor?.Name) {
  58      return { Name: {} }
  59    } else if (descriptor?.Position) {
  60      return { Position: {} }
  61    } else if (descriptor?.Reference) {
  62      return { Reference: {} }
  63    } else if (descriptor?.Unknown) {
  64      return { Unknown: {} }
  65    }
  66  }
  67  
  68  /**
  69   *
  70   * @param {unknown} source
  71   * @returns {API.Scalar|undefined}
  72   */
  73  const fromScalar = (source) => {
  74    switch (typeof source) {
  75      case 'string':
  76      case 'number':
  77      case 'bigint':
  78      case 'boolean':
  79        return source
  80      case 'object':
  81        return source == null || source instanceof Uint8Array ? source : undefined
  82      default:
  83        return undefined
  84    }
  85  }
  86  
  87  /**
  88   *
  89   * @param {object} schema
  90   */
  91  const deriveThe = (schema) =>
  92    base58btc.baseEncode(Link.of(schema)['/'].subarray(-32))
  93  
  94  /**
  95   * @template {string} The
  96   * @template {API.RuleDescriptor} Schema
  97   * @param {Schema & {the?: The, this?: ObjectConstructor | { Entity: {} }, _?: never}} source
  98   * @returns {API.Claim<API.FactView<The, Omit<Schema, 'the'> & { this: ObjectConstructor }>, The, Omit<Schema, 'the'> & { this: ObjectConstructor }, {}>}
  99   */
 100  export const fact = ({ the, ...source }) => {
 101    const members = []
 102    for (const [name, member] of Object.entries({ this: Object, ...source })) {
 103      const descriptor =
 104        fromConstructor(member) ??
 105        fromDescriptor(/** @type {{}|null|undefined} */ (member)) ??
 106        fromScalar(member)
 107  
 108      if (name === '_') {
 109        throw new TypeError(`Schema may no have reserved "_" property`)
 110      }
 111  
 112      if (descriptor === undefined) {
 113        throw new TypeError(
 114          `Unsupported schema member ${toDebugString(/** @type {{}} */ (member))}`
 115        )
 116      } else {
 117        members.push([name, descriptor])
 118      }
 119    }
 120  
 121    if (members.length === 1 && typeof the !== 'string') {
 122      throw new TypeError(
 123        `Schema must contain at least one property. To tag entities you could use "the" discriminant`
 124      )
 125    }
 126  
 127    const schema = Object.fromEntries(members)
 128    the = typeof the === 'string' ? the : /** @type {The} */ (deriveThe(schema))
 129  
 130    if (!schema.this.Entity) {
 131      throw new TypeError(
 132        `Schema may not have "this" property that is not an entity`
 133      )
 134    }
 135  
 136    /** @type {API.Premise<The, Omit<Schema, 'the'> & { this: ObjectConstructor }>} */
 137    const premise = {
 138      the,
 139      schema,
 140      attributes: deriveAttributes(schema),
 141    }
 142  
 143    const conclusion = Fact.for(premise)
 144    const claim = new Claim(premise, conclusion, {})
 145  
 146    return claim
 147  }
 148  
 149  export const claim = fact
 150  
 151  /**
 152   * @template Fact
 153   * @template {string} The
 154   * @template {API.FactSchema} Schema
 155   * @template {API.RuleDescriptor} Context
 156   * @implements {API.Claim<Fact, The, Schema, Context>}
 157   * @extends {Callable<(terms?: API.InferFactTerms<Schema>) => FactMatch<Fact, The, Schema>>}
 158   */
 159  class Claim extends Callable {
 160    /**
 161     * @param {API.Premise<The, Schema>} premise
 162     * @param {API.Conclusion<Fact, The, Schema>} conclusion
 163     * @param {Context} context
 164     */
 165    constructor(premise, conclusion, context) {
 166      super((terms) => this.match(terms))
 167  
 168      this.premise = premise
 169      this.conclusion = conclusion
 170      this.context = context
 171      this.#cells =
 172        /** @type {API.InferSchemaAttributes<Schema & Context> & {_: API.Variable, this: API.Variable<API.Entity>}}  */
 173        (deriveCells({ ...context, ...premise.schema }))
 174    }
 175  
 176    get the() {
 177      return this.premise.the
 178    }
 179    /**
 180     * Map of variables corresponding to the fact members.
 181     *
 182     * @type {API.InferSchemaAttributes<Schema>}
 183     */
 184    get attributes() {
 185      return this.premise.attributes
 186    }
 187  
 188    get schema() {
 189      return this.premise.schema
 190    }
 191  
 192    /** @type {API.InferSchemaAttributes<Schema & Context> & {_: API.Variable, this: API.Variable<API.Entity>}} */
 193    #cells
 194  
 195    get cells() {
 196      return this.#cells
 197    }
 198  
 199    /** @type {API.DeductiveRuleSyntax<API.InferSchemaAttributes<Schema>>|undefined} */
 200    #build
 201  
 202    /**
 203     * Builds a deduction form for the this fact.
 204     */
 205    build() {
 206      if (!this.#build) {
 207        const { the, schema, attributes } = this.premise
 208        const where = []
 209        for (const name of Object.keys(schema)) {
 210          if (name !== 'this') {
 211            where.push({
 212              match: {
 213                the: `${the}/${name}`,
 214                of: attributes.this,
 215                is: attributes[name],
 216              },
 217              fact: {},
 218            })
 219          }
 220        }
 221  
 222        // If we have no other fields we should still be able to use fact as a
 223        // tag which is why we add a predicate for that case.
 224        if (where.length === 0) {
 225          where.push({
 226            match: {
 227              the: `the/${the}`,
 228              of: attributes.this,
 229              is: the,
 230            },
 231          })
 232        }
 233  
 234        this.#build = Syntax.rule({ match: attributes, when: { where } })
 235      }
 236      return this.#build
 237    }
 238  
 239    /**
 240     * @param {API.InferSchemaTerms<Schema>} terms
 241     * @returns {API.RuleApplicationSyntax<API.InferSchemaAttributes<Schema>>}
 242     */
 243    apply(terms) {
 244      return this.build().apply(terms)
 245    }
 246  
 247    /**
 248     * @param {API.InferSchemaTerms<Schema>} terms
 249     * @returns {API.MatchView}
 250     */
 251    recur(terms) {
 252      return [this.apply(terms)]
 253    }
 254  
 255    /**
 256     * Creates predicate for this fact that matches given terms.
 257     *
 258     * @param {Partial<API.InferFactTerms<Schema>>} [terms]
 259     * @returns {FactMatch<Fact, The, Schema>}
 260     */
 261    match(terms = {}) {
 262      return new FactMatch(
 263        this.premise,
 264        this.conclusion,
 265        this,
 266        completeTerms(this.premise.schema, terms)
 267      )
 268    }
 269  
 270    /**
 271     * Creates a predicate for this fact that excludes ones that match given
 272     * terms.
 273     *
 274     * @param {Partial<API.InferSchemaTerms<Schema>>} terms
 275     * @returns {Negation<Fact, The, Schema>}
 276     */
 277    not(terms) {
 278      return new Negation(this.premise, this.conclusion, this, terms)
 279    }
 280  
 281    /**
 282     * Asserts this fact with a given data. If data does not conforms this fact
 283     * throws an error.
 284     *
 285     * @param {API.InferAssert<Schema>} fact
 286     * @returns {Fact}
 287     */
 288    assert(fact) {
 289      return this.conclusion.assert(fact)
 290    }
 291  
 292    /**
 293     * Defines local variables so they could be used in the `.when` and `.where`
 294     * methods without makeing those part of the fact.
 295     *
 296     * @template {Exclude<API.RuleDescriptor, Schema & Context>} Extension
 297     * @param {Extension} extension
 298     * @returns {API.Claim<Fact, The, Schema, Context & Extension>}
 299     */
 300    with(extension) {
 301      return new Claim(this.premise, this.conclusion, {
 302        ...extension,
 303        ...this.context,
 304      })
 305    }
 306  
 307    /**
 308     * Defines a rule that deduces this fact whenever any of the branches are true.
 309     * Takes a `build` function that will be given set of variables corresponding
 310     * to the fact members which must return object where keys represent disjuncts
 311     * and values are arrays representing conjuncts for those disjuncts. In other
 312     * works each member of the returned object represent OR branches where each
 313     * branch is an AND joined predicates by passed variables.
 314     *
 315     * @param {API.SomeBuilder<Schema & Context>} compile
 316     * @returns {API.Deduction<Fact, The, Schema, Context>}
 317     */
 318    when(compile) {
 319      return new Deduction(this.premise, this.conclusion, this.context, compile)
 320    }
 321  
 322    /**
 323     * Defines a rule that dudces this fact whenever all of the predicates are
 324     * true. This is a shortuct of `.when` which is convinient in cases where
 325     * only one branch is needed.
 326     *
 327     * @param {API.EveryBuilder<Schema & Context>} compile
 328     * @returns {API.Deduction<Fact, The, Schema, Context>}
 329     */
 330    where(compile) {
 331      return new Deduction(this.premise, this.conclusion, this.context, compile)
 332    }
 333  
 334    /**
 335     * This method can be used to project different layout of the selected facts,
 336     * it is maraked deprecated, but it is not deprecated but rather an
 337     * experimental and may be removed at any point. This method can be used as an
 338     * aggregator to group results, however this leads to nested structures which
 339     * are at odds with the facts that are flat records.
 340     *
 341     * @deprecated
 342     *
 343     * @template {API.Selector} Terms
 344     * @param {API.ProjectionBuilder<Schema & Context, Terms>} compile
 345     * @returns {Select<The, Schema, Context, Terms>}
 346     */
 347    select(compile = (variables) => /** @type {Terms} */ (variables)) {
 348      return new Select(this.premise, this.build(), this.cells, compile)
 349    }
 350  
 351    /**
 352     * @template View
 353     * @param {(fact: Fact) => View} mapper
 354     * @returns {API.Claim<View, The, Schema, Context>}
 355     */
 356    map(mapper) {
 357      return new Claim(
 358        this.premise,
 359        {
 360          assert: (fact) => {
 361            return mapper(this.conclusion.assert(fact))
 362          },
 363        },
 364        this.context
 365      )
 366    }
 367  
 368    /**
 369     * @template View, State
 370     * @param {API.Aggregator<View, Fact, State>} aggregator
 371     */
 372    aggregate(aggregator) {
 373      return new Aggregation(this, aggregator)
 374    }
 375  }
 376  
 377  /**
 378   * @template Fact
 379   * @template {string} The
 380   * @template {API.FactSchema} Schema
 381   * @template {API.RuleDescriptor} Context
 382   * @implements {API.Deduction<Fact, The, Schema, Context>}
 383   * @extends {Claim<Fact, The, Schema, Context>}
 384   */
 385  class Deduction extends Claim {
 386    /**
 387     * @param {API.Premise<The, Schema>} premise
 388     * @param {API.Conclusion<Fact, The, Schema>} conclusion
 389     * @param {Context} context
 390     * @param {API.WhenBuilder<Schema & Context>} compile
 391     */
 392    constructor(premise, conclusion, context, compile) {
 393      super(premise, conclusion, context)
 394      this.compile = compile
 395    }
 396  
 397    /**
 398     * If rule is applied recursively we want to return `Recur` as opposed to
 399     * plain `FactMatch` to accomplish this we set this property to `this`
 400     * during `source` compilation. This works because when/where `build` function
 401     * is called during compilation which in turn ends up calling `match` method
 402     * that looks at `this.#circuit` to decide which one to construct. Furthermore
 403     * we do pass this `#circuit` to it during construction.
 404     *
 405     * @type {Circuit<Schema, Fact>|null}
 406     */
 407    self = null
 408  
 409    /** @type {API.DeductiveRuleSyntax<API.InferSchemaAttributes<Schema>>|undefined} */
 410    #build
 411    build() {
 412      if (!this.#build) {
 413        this.self = this
 414  
 415        const when = /** @type {Record<string, API.Every>} */ ({})
 416        for (const [name, disjunct] of iterateDisjuncts(
 417          this.compile(this.cells)
 418        )) {
 419          when[name] = /** @type {[API.Conjunct, ...API.Conjunct[]]} */ ([
 420            ...iterateConjuncts(disjunct),
 421          ])
 422        }
 423  
 424        this.#build = Syntax.rule({
 425          match: this.premise.attributes,
 426          when: /** @type {API.Some} */ (when),
 427        })
 428  
 429        this.self = null
 430      }
 431  
 432      return this.#build
 433    }
 434  
 435    /**
 436     * @param {API.InferSchemaTerms<Schema>} terms
 437     * @returns {API.RuleApplicationSyntax<API.InferSchemaAttributes<Schema>>}
 438     */
 439    apply(terms) {
 440      return this.build().apply(terms)
 441    }
 442  
 443    /**
 444     * @param {API.InferSchemaTerms<Schema>} terms
 445     * @returns {API.MatchView}
 446     */
 447    recur(terms) {
 448      return [
 449        /** @type {API.Recur} */ ({
 450          recur: terms,
 451        }),
 452      ]
 453    }
 454  
 455    /**
 456     * @param {API.InferClaimTerms<Schema>} terms
 457     */
 458    induce(terms) {
 459      const { premise, cells } = this
 460      const { schema, the } = premise
 461      const predicates = []
 462      for (const [name, member] of Object.entries(schema)) {
 463        const [term, cell] = [terms[name], cells[name]]
 464        // If `this` was not provided we derive one from the data itself.
 465        if (name === 'this' && !term) {
 466          predicates.push(
 467            Data.Fact({
 468              .../** @type {Record<string, API.Term>} */ (terms),
 469              the,
 470              this: cells.this,
 471            })
 472          )
 473        } else {
 474          predicates.push(
 475            ...same({ this: /** @type {API.Scalar} */ (term), as: cell })
 476          )
 477        }
 478      }
 479  
 480      return new Constraint(predicates)
 481    }
 482  
 483    /**
 484     * @param {Partial<API.InferFactTerms<Schema>>} [terms]
 485     * @returns {FactMatch<Fact, The, Schema>}
 486     */
 487    match(terms) {
 488      // 🤔 If it is recursion we can't just generate new variables
 489      // we need to reuse ones from the current context.
 490      const { premise, conclusion, self } = this
 491      const match =
 492        self ?
 493          new Recur(premise, conclusion, self ?? this, {
 494            ...this.attributes,
 495            ...terms,
 496          })
 497        : new FactMatch(
 498            premise,
 499            conclusion,
 500            self ?? this,
 501            completeTerms(premise.schema, terms)
 502          )
 503      return match
 504    }
 505  
 506    /**
 507     * @param {API.InferClaimTerms<Schema>} fact
 508     */
 509    claim(fact) {
 510      return this.induce(fact)
 511    }
 512  
 513    /** @type {Induction<Fact, The, Schema, Context>|undefined} */
 514    #induction
 515    get inductive() {
 516      if (!this.#induction) {
 517        const { self } = this
 518        const induction = new Induction(
 519          this.premise,
 520          this.conclusion,
 521          this.context,
 522          this.compile
 523        )
 524        this.self = induction
 525        // Force induction compilaction so that it will be the self in the given
 526        // context.
 527        induction.build()
 528        // Then we reset the self so that it continues to behave as intended.
 529        this.self = self
 530        // cache the instance
 531        this.#induction = induction
 532      }
 533  
 534      return this.#induction
 535    }
 536  
 537    /**
 538     * @template View
 539     * @param {(fact: Fact) => View} mapper
 540     * @returns {API.Deduction<View, The, Schema, Context>}
 541     */
 542    map(mapper) {
 543      return new Deduction(
 544        this.premise,
 545        {
 546          assert: (fact) => {
 547            return mapper(this.conclusion.assert(fact))
 548          },
 549        },
 550        this.context,
 551        this.compile
 552      )
 553    }
 554  }
 555  
 556  /**
 557   * @template Fact
 558   * @template {string} The
 559   * @template {API.RuleDescriptor & {this: ObjectConstructor}} Schema
 560   * @template {API.RuleDescriptor} Locals
 561   * @extends {Deduction<Fact, The, Schema, Locals>}
 562   */
 563  class Induction extends Deduction {
 564    /**
 565     * @param {API.InferSchemaTerms<Schema>} terms
 566     * @returns {API.MatchView}
 567     */
 568    recur(terms) {
 569      return this.induce(terms)
 570    }
 571  }
 572  
 573  /**
 574   * @template View
 575   * @template Fact
 576   * @template State
 577   * @template {string} The
 578   * @template {API.FactSchema} Schema
 579   * @template {API.RuleDescriptor} Context
 580   * @implements {API.Aggregation<View, Fact, The, Schema>}
 581   * @extends {Callable<(terms?: API.InferFactTerms<Schema>) => API.Aggregate<View>>}
 582   */
 583  class Aggregation extends Callable {
 584    /**
 585     * @param {Claim<Fact, The, Schema, Context>} rule
 586     * @param {API.Aggregator<View, Fact, State>} aggregator
 587     */
 588    constructor(rule, aggregator) {
 589      super((terms) => this.match(terms))
 590      this.rule = rule
 591      this.aggregator = aggregator
 592    }
 593  
 594    get the() {
 595      return this.rule.the
 596    }
 597    get attributes() {
 598      return this.rule.attributes
 599    }
 600    get schema() {
 601      return this.rule.schema
 602    }
 603    get cells() {
 604      return this.rule.cells
 605    }
 606  
 607    /**
 608     * @param {API.InferSchemaTerms<Schema>} terms
 609     */
 610    apply(terms) {
 611      return this.rule.apply(terms)
 612    }
 613  
 614    /**
 615     * @param {API.InferSchemaTerms<Schema>} terms
 616     */
 617    recur(terms) {
 618      return this.rule.recur(terms)
 619    }
 620    /**
 621     * Creates a predicate for this fact that excludes ones that match given
 622     * terms.
 623     *
 624     * @param {Partial<API.InferSchemaTerms<Schema>>} terms
 625     * @returns {Negation<Fact, The, Schema>}
 626     */
 627    not(terms) {
 628      return this.rule.not(terms)
 629    }
 630  
 631    /**
 632     * Asserts this fact with a given data. If data does not conforms this fact
 633     * throws an error.
 634     *
 635     * @param {API.InferAssert<Schema>} fact
 636     * @returns {Fact}
 637     */
 638    assert(fact) {
 639      return this.rule.assert(fact)
 640    }
 641    /**
 642     * Creates predicate for this fact that matches given terms.
 643     *
 644     * @param {Partial<API.InferFactTerms<Schema>>} [terms]
 645     * @returns {Aggregate<View, Fact, State, The, Schema>}
 646     */
 647    match(terms = {}) {
 648      return new Aggregate(
 649        this.rule.premise,
 650        this.rule.conclusion,
 651        this,
 652        this.aggregator,
 653        completeTerms(this.schema, terms)
 654      )
 655    }
 656  }
 657  /**
 658   *
 659   * @template {API.FactSchema} Schema
 660   * @template Fact
 661   * @typedef {object} Circuit
 662   * @property {API.InferSchemaAttributes<Schema>} cells
 663   * @property {(terms: API.InferSchemaTerms<Schema>) => API.RuleApplicationSyntax<API.InferSchemaAttributes<Schema>>} apply
 664   * @property {(terms: API.InferSchemaTerms<Schema>) => API.MatchView} recur
 665   * @property {(claim: API.InferAssert<Schema>) => Fact} assert
 666   */
 667  
 668  /**
 669   * @template Fact
 670   * @template {string} The
 671   * @template {API.RuleDescriptor & {this: ObjectConstructor}} Schema
 672   * @implements {API.MatchView<unknown>}
 673   */
 674  class Negation {
 675    /**
 676     * @param {API.Premise<The, Schema>} premise
 677     * @param {API.Conclusion<Fact, The, Schema>} conclusion
 678     * @param {Circuit<Schema, Fact>} rule
 679     * @param {Partial<API.InferSchemaTerms<Schema>>} terms
 680     */
 681    constructor(premise, conclusion, rule, terms) {
 682      this.premise = premise
 683      this.conclusion = conclusion
 684      this.rule = rule
 685      this.terms = terms
 686    }
 687    /** @type {API.NegationSyntax|undefined} */
 688    #build
 689    build() {
 690      if (!this.#build) {
 691        this.#build = this.rule
 692          .apply(
 693            // This not true, but apply does not actually need all terms we only
 694            // type it this way to make lower level API less error prone. Perhaps
 695            // we need to revise it to having to lie to it.
 696            /** @type {API.InferSchemaTerms<Schema>} */ (this.terms)
 697          )
 698          .negate()
 699      }
 700      return this.#build
 701    }
 702  
 703    toJSON() {
 704      return this.build().toJSON()
 705    }
 706  
 707    *[Symbol.iterator]() {
 708      yield this.build()
 709    }
 710  }
 711  
 712  /**
 713   * @template {API.RuleDescriptor} Schema
 714   */
 715  class Match {
 716    /**
 717     * @param {API.DeductiveRuleSyntax<API.InferSchemaAttributes<Schema>>} rule
 718     * @param {API.InferSchemaTerms<Schema>} terms
 719     */
 720    constructor(rule, terms) {
 721      this.rule = rule
 722      this.terms = terms
 723    }
 724  
 725    /** @type {API.RuleApplicationSyntax<API.InferSchemaAttributes<Schema>>|undefined} */
 726    #form
 727    get form() {
 728      if (!this.#form) {
 729        this.#form = this.rule.apply(this.terms)
 730      }
 731      return this.#form
 732    }
 733  
 734    *[Symbol.iterator]() {
 735      yield this.form
 736    }
 737  
 738    /** @type {API.RuleApplicationPlan<API.InferSchemaAttributes<Schema>>|undefined} */
 739    #plan
 740    get plan() {
 741      if (!this.#plan) {
 742        this.#plan = this.form.prepare()
 743      }
 744  
 745      return this.#plan
 746    }
 747  
 748    toJSON() {
 749      return this.form.toJSON()
 750    }
 751    /**
 752     * @param {{ from: API.Querier }} source
 753     */
 754    *execute(source) {
 755      const selection = yield* this.plan.query(source)
 756      return Selector.select(this.terms, selection)
 757    }
 758  
 759    /**
 760     * @param {{ from: API.Querier }} source
 761     */
 762    query(source) {
 763      // 😵‍💫 Here we force plan compilation because we want to get planning error
 764      // before we get a
 765      this.plan
 766      return Task.perform(this.execute(source))
 767    }
 768  }
 769  
 770  /**
 771   * @template Fact
 772   * @template {string} The
 773   * @template {API.RuleDescriptor & {this: ObjectConstructor}} Schema
 774   * @implements {API.MatchView<unknown>}
 775   */
 776  class FactMatch {
 777    /**
 778     * @param {API.Premise<The, Schema>} premise
 779     * @param {API.Conclusion<Fact, The, Schema>} conclusion
 780     * @param {Circuit<Schema, Fact>} rule
 781     * @param {API.InferSchemaTerms<Schema>} terms
 782     */
 783    constructor(premise, conclusion, rule, terms) {
 784      this.premise = premise
 785      this.conclusion = conclusion
 786      this.rule = rule
 787      this.terms = terms
 788    }
 789  
 790    /**
 791     * @returns {Negation<Fact, The, Schema>}
 792     */
 793    negate() {
 794      return new Negation(this.premise, this.conclusion, this.rule, this.terms)
 795    }
 796  
 797    /** @type {API.RuleApplicationSyntax<API.InferSchemaAttributes<Schema>>|undefined} */
 798    #build
 799    build() {
 800      if (!this.#build) {
 801        this.#build = this.rule.apply(this.terms)
 802      }
 803      return this.#build
 804    }
 805    /** @type {API.RuleApplicationPlan<API.InferSchemaAttributes<Schema>>|undefined} */
 806    #plan
 807    plan() {
 808      if (!this.#plan) {
 809        this.#plan = this.rule.apply(this.terms).prepare()
 810      }
 811      return this.#plan
 812    }
 813  
 814    /** @returns {Iterator<API.Conjunct|API.Recur>} */
 815    *[Symbol.iterator]() {
 816      yield this.build()
 817    }
 818  
 819    toJSON() {
 820      return this.build().toJSON()
 821    }
 822    /**
 823     * @param {API.Task<API.MatchFrame[], Error>} query
 824     */
 825    *execute(query) {
 826      const { terms } = this
 827      const selection = yield* query
 828  
 829      const facts = []
 830      for (const match of selection) {
 831        /** @type {Record<string, API.Scalar>} */
 832        const model = {}
 833        for (const [key, term] of Object.entries(terms)) {
 834          model[key] = /** @type {API.Scalar} */ (
 835            Variable.is(term) ? match.get(term) : term
 836          )
 837        }
 838  
 839        model.the = this.premise.the
 840        const fact = this.conclusion.assert(
 841          /** @type {API.InferFact<Schema> & { this: API.Entity, the: The }} */ (
 842            model
 843          )
 844        )
 845  
 846        facts.push(fact)
 847      }
 848  
 849      return facts
 850    }
 851  
 852    /**
 853     * @param {{ from: API.Querier }} source
 854     */
 855    query(source) {
 856      return Task.perform(this.execute(this.plan().query(source)))
 857    }
 858  }
 859  
 860  /**
 861   * @template View
 862   * @template Fact
 863   * @template State
 864   * @template {string} The
 865   * @template {API.RuleDescriptor & {this: ObjectConstructor}} Schema
 866   */
 867  class Aggregate {
 868    /**
 869     * @param {API.Premise<The, Schema>} premise
 870     * @param {API.Conclusion<Fact, The, Schema>} conclusion
 871     * @param {Circuit<Schema, Fact>} rule
 872     * @param {API.Aggregator<View, Fact, State>} aggregator
 873     * @param {API.InferSchemaTerms<Schema>} terms
 874     */
 875    constructor(premise, conclusion, rule, aggregator, terms) {
 876      this.premise = premise
 877      this.conclusion = conclusion
 878      this.rule = rule
 879      this.aggregator = aggregator
 880      this.terms = terms
 881    }
 882    /**
 883     * @returns {Negation<Fact, The, Schema>}
 884     */
 885    negate() {
 886      return new Negation(this.premise, this.conclusion, this.rule, this.terms)
 887    }
 888    /** @type {API.RuleApplicationSyntax<API.InferSchemaAttributes<Schema>>|undefined} */
 889    #build
 890    build() {
 891      if (!this.#build) {
 892        this.#build = this.rule.apply(this.terms)
 893      }
 894      return this.#build
 895    }
 896    /** @type {API.RuleApplicationPlan<API.InferSchemaAttributes<Schema>>|undefined} */
 897    #plan
 898    plan() {
 899      if (!this.#plan) {
 900        this.#plan = this.rule.apply(this.terms).prepare()
 901      }
 902      return this.#plan
 903    }
 904  
 905    /** @returns {Iterator<API.Conjunct|API.Recur>} */
 906    *[Symbol.iterator]() {
 907      yield this.build()
 908    }
 909  
 910    toJSON() {
 911      return this.build().toJSON()
 912    }
 913    /**
 914     * @param {API.Task<API.MatchFrame[], Error>} query
 915     * @returns {API.Task<View, Error>}
 916     */
 917    *execute(query) {
 918      const { terms, aggregator } = this
 919      const selection = yield* query
 920  
 921      let aggregate = aggregator.open()
 922      for (const match of selection) {
 923        /** @type {Record<string, API.Scalar>} */
 924        const model = {}
 925        for (const [key, term] of Object.entries(terms)) {
 926          model[key] = /** @type {API.Scalar} */ (
 927            Variable.is(term) ? match.get(term) : term
 928          )
 929        }
 930  
 931        model.the = this.premise.the
 932        const fact = this.conclusion.assert(
 933          /** @type {API.InferFact<Schema> & { this: API.Entity, the: The }} */ (
 934            model
 935          )
 936        )
 937  
 938        aggregate = aggregator.merge(aggregate, fact)
 939      }
 940  
 941      return aggregator.close(aggregate)
 942    }
 943  
 944    /**
 945     * @param {{ from: API.Querier }} source
 946     */
 947    query(source) {
 948      return Task.perform(this.execute(this.plan().query(source)))
 949    }
 950  }
 951  /**
 952   * Subclass of {@link FactMatch} that represents a recursive rule application.
 953  
 954   * @template {string} The
 955   * @template {API.RuleDescriptor & {this: ObjectConstructor}} Schema
 956   * @template Fact
 957   * @extends {FactMatch<Fact, The, Schema>}
 958   */
 959  class Recur extends FactMatch {
 960    /**
 961     * We override the itertor to yield recursion form as opposed to application
 962     * from.
 963     */
 964    *[Symbol.iterator]() {
 965      yield* this.rule.recur(this.terms)
 966    }
 967  }
 968  
 969  /**
 970   * @template {string} The
 971   * @template {API.FactSchema} Schema
 972   */
 973  class Fact {
 974    /**
 975     * @template {string} The
 976     * @template {API.FactSchema} Schema
 977     * @param {API.Premise<The, Schema>} premise
 978     * @returns {API.Conclusion<API.FactView<The, Schema>, The, Schema>}
 979     */
 980    static for({ the, schema }) {
 981      const This = this
 982  
 983      /**
 984       * @extends {This<The, Schema>}
 985       */
 986      class Fact extends this {
 987        static the = /** @type {The} */ (the)
 988        static schema = schema
 989      }
 990  
 991      return Fact
 992    }
 993    /**
 994     * @template {string} The
 995     * @template {API.FactSchema} Schema
 996     * @this {{the: The, schema: Schema} & typeof Fact} this
 997     * @param {Record<string, API.Scalar>} claim
 998     * @returns {API.FactView<The, Schema>}
 999     */
1000    static assert({ the, this: entity, ...attributes }) {
1001      if (the != null && the !== this.the) {
1002        throw new TypeError(
1003          `Optional attribute "the", if set must match the schema vaule "${this.the}"`
1004        )
1005      }
1006  
1007      // Validate that all attributes have being provided
1008      // TODO: Do actual schema validation to ensure tha attributes do
1009      // conform to the schema
1010      for (const name of Object.keys(this.schema)) {
1011        const value = attributes[name]
1012        if (value === undefined && name !== 'this') {
1013          throw new TypeError(`Required attribute "${name}" is missing`)
1014        }
1015      }
1016  
1017      const fact = new this(
1018        this.the,
1019        /** @type {API.Entity} */ (
1020          entity ?? Link.of({ ...attributes, the: this.the })
1021        ),
1022        /** @type {Omit<API.InferAssert<Schema>, 'this'|'the'>} */ (attributes)
1023      )
1024  
1025      return /** @type {API.FactView<The, Schema>} */ (fact)
1026    }
1027  
1028    /**
1029     * @param {The} the
1030     * @param {API.Entity} self
1031     * @param {Omit<API.InferAssert<Schema>, 'this'|'the'>} attributes
1032     */
1033    constructor(the, self, attributes) {
1034      this.#the = the
1035      this.#attributes = attributes
1036      this.this = self
1037  
1038      Object.assign(this, attributes)
1039    }
1040    #attributes
1041    get attributes() {
1042      return this.#attributes
1043    }
1044  
1045    #the
1046    get the() {
1047      return this.#the
1048    }
1049  
1050    *[Symbol.iterator]() {
1051      const { the, this: of } = this
1052  
1053      yield {
1054        assert: { the: `the/${the}`, of, is: the },
1055      }
1056  
1057      for (const [name, is] of Object.entries(this.#attributes)) {
1058        yield {
1059          assert: {
1060            the: `${the}/${name}`,
1061            of,
1062            is,
1063          },
1064        }
1065      }
1066    }
1067  
1068    /**
1069     * @returns {IterableIterator<{retract: API.Fact}>}
1070     */
1071    *retract() {
1072      const { the, this: of } = this
1073      yield {
1074        retract: { the: `the/${the}`, of: of, is: the },
1075      }
1076  
1077      for (const [name, value] of Object.entries(this.#attributes)) {
1078        if (name !== 'this' && name !== 'the') {
1079          yield {
1080            retract: { the: `${the}/${name}`, of, is: value },
1081          }
1082        }
1083      }
1084    }
1085  
1086    toJSON() {
1087      return { ...this, the: this.the, this: JSON.from(this.this) }
1088    }
1089  }
1090  
1091  /**
1092   * @implements {API.MatchView<unknown>}
1093   */
1094  class Constraint {
1095    /**
1096     * @param {API.Conjunct[]} predicates
1097     */
1098    constructor(predicates) {
1099      this.predicates = predicates
1100    }
1101    *[Symbol.iterator]() {
1102      yield* this.predicates
1103    }
1104  }
1105  
1106  /**
1107   * @template {string} The
1108   * @template {API.FactSchema} Schema
1109   * @template {API.RuleDescriptor} Context
1110   * @template {API.Selector} Selector
1111   * @implements {API.Projection<Schema, Selector>}
1112   * @extends {Callable<(terms?: API.InferFactTerms<Schema>) => GroupedSelection<The, Schema, Selector>>}
1113   */
1114  class Select extends Callable {
1115    /**
1116     * @param {API.Premise<The, Schema>} premise
1117     * @param {API.DeductiveRuleSyntax<API.InferSchemaAttributes<Schema>>} rule
1118     * @param {API.InferSchemaAttributes<Schema & Context> & {_: API.Variable; this: API.Variable<API.Entity>}} cells
1119  }}
1120     * @param {API.ProjectionBuilder<Schema & Context, Selector>} compile
1121     */
1122    constructor(premise, rule, cells, compile) {
1123      super((terms) => this.match(terms))
1124      this.premise = premise
1125      this.cells = cells
1126      this.rule = rule
1127      this.compile = compile
1128    }
1129  
1130    /** @type {Selector|undefined} */
1131    #build
1132    build() {
1133      if (!this.#build) {
1134        this.#build = this.compile(this.cells)
1135      }
1136      return this.#build
1137    }
1138  
1139    /**
1140     * @param {Partial<API.InferFactTerms<Schema>>} [terms]
1141     * @returns {GroupedSelection<The, Schema, Selector>}
1142     */
1143    match(terms) {
1144      return new GroupedSelection(this.premise, this.build(), this.rule, {
1145        ...this.cells,
1146        ...terms,
1147      })
1148    }
1149  }
1150  
1151  /**
1152   * @template {string} The
1153   * @template {API.RuleDescriptor & {this: ObjectConstructor}} Schema
1154   * @template {API.Selector} Selector
1155   */
1156  class GroupedSelection {
1157    /**
1158     * @param {API.Premise<The, Schema>} premise
1159     * @param {Selector} selector
1160     * @param {API.DeductiveRuleSyntax<API.InferSchemaAttributes<Schema>>} rule
1161     * @param {API.InferSchemaTerms<Schema>} terms
1162     */
1163    constructor(premise, selector, rule, terms) {
1164      this.premise = premise
1165      this.selector = selector
1166      this.rule = rule
1167      this.terms = terms
1168    }
1169  
1170    /** @type {API.RuleApplicationSyntax<API.InferSchemaAttributes<Schema>>|undefined} */
1171    #form
1172    get form() {
1173      if (!this.#form) {
1174        this.#form = this.rule.apply(
1175          /** @type {API.InferSchemaTerms<Schema>} */ (this.terms)
1176        )
1177      }
1178      return this.#form
1179    }
1180  
1181    *[Symbol.iterator]() {
1182      yield this.form
1183    }
1184  
1185    /** @type {API.RuleApplicationPlan<API.InferSchemaAttributes<Schema>>|undefined} */
1186    #plan
1187    plan() {
1188      if (!this.#plan) {
1189        this.#plan = this.rule.apply(this.terms).prepare()
1190      }
1191  
1192      return this.#plan
1193    }
1194  
1195    toJSON() {
1196      return this.form.toJSON()
1197    }
1198    /**
1199     * @param {API.Task<API.MatchFrame[], Error>} query
1200     */
1201    *execute(query) {
1202      const selection = yield* query
1203      return Selector.select(this.selector, selection)
1204    }
1205  
1206    /**
1207     * @param {{ from: API.Querier }} source
1208     */
1209    query(source) {
1210      return Task.perform(this.execute(this.plan().query(source)))
1211    }
1212  }
1213  
1214  export { $, _ }
1215  /**
1216   * @template Terms
1217   * @template {(terms: any) => API.Constraint} F
1218   * @extends {Callable<F>}
1219   */
1220  export class Operator extends Callable {
1221    /**
1222     * @template Terms
1223     * @template {(terms: Terms) => API.Constraint} Formula
1224     * @param {Formula} match
1225     * @returns {Operator<Terms, Formula>}
1226     */
1227    static for(match) {
1228      return new this(match)
1229    }
1230    /**
1231     * @param {F} match
1232     */
1233    constructor(match) {
1234      super(match)
1235      this.match = match
1236    }
1237  
1238    /**
1239     * @param {Terms} terms
1240     * @returns {API.Negation}
1241     */
1242    not(terms) {
1243      return { not: this.match(terms) }
1244    }
1245  }
1246  
1247  export const Collection = Operator.for(
1248    /**
1249     * @template {API.Scalar} Member
1250     * @param {object} terms
1251     * @param {API.Term<API.Entity>} terms.this
1252     * @param {API.Term<Member>} terms.of
1253     * @param {API.Term<string>} [terms.at]
1254     */
1255    (terms) => ({
1256      match: { the: terms.at, of: terms.this, is: terms.of },
1257      fact: {},
1258    })
1259  )
1260  
1261  /**
1262   * @param {API.Term<string>} term
1263   */
1264  export const text = (term) => new TextVariable(term)
1265  
1266  class TextVariable {
1267    #this
1268    /**
1269     * @param {API.Term<string>} term
1270     */
1271    constructor(term) {
1272      this.#this = term
1273    }
1274  
1275    /**
1276     * @param {API.Term<string>} pattern
1277     */
1278    like(pattern) {
1279      return Text.match({ this: this.#this, pattern: pattern })
1280    }
1281  
1282    /**
1283     * @param {API.Term<string>} slice
1284     */
1285    includes(slice) {
1286      return Text.includes({ this: this.#this, slice })
1287    }
1288  
1289    /**
1290     * @param {object} terms
1291     * @param {API.Term<string>} terms.with
1292     * @param {API.Term<string>} terms.is
1293     */
1294    concat(terms) {
1295      return Text.Concat({ of: [this.#this, terms.with], is: terms.is })
1296    }
1297  
1298    words() {
1299      const of = this.#this
1300      return {
1301        /**
1302         * @param {API.Term<string>} is
1303         */
1304        is(is) {
1305          return Text.Words({ of, is })
1306        },
1307      }
1308    }
1309  
1310    lines() {
1311      const of = this.#this
1312      return {
1313        /**
1314         * @param {API.Term<string>} is
1315         */
1316        is(is) {
1317          return Text.Lines({ of, is })
1318        },
1319      }
1320    }
1321  
1322    toUpperCase() {
1323      const of = this.#this
1324      return {
1325        /**
1326         * @param {API.Term<string>} is
1327         */
1328        is(is) {
1329          return Text.UpperCase({ of, is })
1330        },
1331        /**
1332         * @param {API.Term<string>} is
1333         */
1334        not(is) {
1335          return { not: Text.UpperCase({ of, is }) }
1336        },
1337      }
1338    }
1339  
1340    /**
1341     */
1342    toLowerCase() {
1343      const of = this.#this
1344      return {
1345        /**
1346         * @param {API.Term<string>} is
1347         */
1348        is(is) {
1349          return Text.LowerCase({ of, is })
1350        },
1351        /**
1352         * @param {API.Term<string>} is
1353         */
1354        not(is) {
1355          return { not: Text.LowerCase({ of, is }) }
1356        },
1357      }
1358    }
1359  
1360    /**
1361     * @param {API.Term<string>} is
1362     */
1363    trim(is) {
1364      return Text.Trim({ of: this.#this, is })
1365    }
1366  }
1367  
1368  export class Text {
1369    #this
1370    /**
1371     * @param {API.Term<string>} source
1372     */
1373    constructor(source) {
1374      this.#this = source
1375    }
1376  
1377    static match = Operator.for(
1378      /**
1379       * @param {object} terms
1380       * @param {API.Term<string>} terms.this
1381       * @param {API.Term<string>} terms.pattern
1382       */
1383      ({ this: text, pattern: like }) => ({
1384        match: { text, pattern: like },
1385        operator: /** @type {const} */ ('text/like'),
1386      })
1387    )
1388  
1389    /**
1390     * @param {object} terms
1391     * @param {API.Term<string>} terms.this
1392     * @param {API.Term<string>} terms.pattern
1393     */
1394    static not(terms) {
1395      return { not: this.match(terms) }
1396    }
1397  
1398    static includes = Operator.for(
1399      /**
1400       * @param {object} terms
1401       * @param {API.Term<string>} terms.this
1402       * @param {API.Term<string>} terms.slice
1403       */
1404      ({ this: source, slice }) => {
1405        return {
1406          match: { this: source, slice },
1407          operator: /** @type {const} */ ('text/includes'),
1408        }
1409      }
1410    )
1411  
1412    static Concat = Operator.for(
1413      /**
1414       * @param {object} terms
1415       * @param {[left:API.Term<string>, right: API.Term<string>]} terms.of
1416       * @param {API.Term<string>} [terms.is]
1417       * @returns {API.SystemOperator}
1418       */
1419      ({ of: [left, right], is }) => {
1420        return {
1421          match: { of: left, with: right, is },
1422          operator: /** @type {const} */ ('text/concat'),
1423        }
1424      }
1425    )
1426  
1427    static Words = Operator.for(
1428      /**
1429       * @param {object} terms
1430       * @param {API.Term<string>} terms.of
1431       * @param {API.Term<string>} [terms.is]
1432       */
1433      ({ of, is }) => {
1434        return {
1435          match: { of, is },
1436          operator: /** @type {const} */ ('text/words'),
1437        }
1438      }
1439    )
1440  
1441    static Lines = Operator.for(
1442      /**
1443       * @param {object} terms
1444       * @param {API.Term<string>} terms.of
1445       * @param {API.Term<string>} [terms.is]
1446       */
1447      ({ of, is }) => {
1448        return {
1449          match: { of, is },
1450          operator: /** @type {const} */ ('text/lines'),
1451        }
1452      }
1453    )
1454  
1455    static UpperCase = Operator.for(
1456      /**
1457       * @param {object} terms
1458       * @param {API.Term<string>} terms.of
1459       * @param {API.Term<string>} [terms.is]
1460       */
1461      ({ of, is }) => {
1462        return {
1463          match: { of, is },
1464          operator: /** @type {const} */ ('text/case/upper'),
1465        }
1466      }
1467    )
1468  
1469    static LowerCase = Operator.for(
1470      /**
1471       * @param {object} terms
1472       * @param {API.Term<string>} terms.of
1473       * @param {API.Term<string>} [terms.is]
1474       *
1475       */
1476      ({ of, is }) => {
1477        return {
1478          match: { of, is },
1479          operator: /** @type {const} */ ('text/case/lower'),
1480        }
1481      }
1482    )
1483  
1484    static Trim = Operator.for(
1485      /**
1486       * @param {object} terms
1487       * @param {API.Term<string>} terms.of
1488       * @param {API.Term<string>} [terms.is]
1489       */
1490      ({ of, is }) => {
1491        return {
1492          match: { of, is },
1493          operator: /** @type {const} */ ('text/trim'),
1494        }
1495      }
1496    )
1497  
1498    static TrimStart = Operator.for(
1499      /**
1500       * @param {object} terms
1501       * @param {API.Term<string>} terms.of
1502       * @param {API.Term<string>} [terms.is]
1503       */
1504      ({ of, is }) => {
1505        return {
1506          match: { of, is },
1507          operator: /** @type {const} */ ('text/trim/start'),
1508        }
1509      }
1510    )
1511  
1512    static TrimEnd = Operator.for(
1513      /**
1514       * @param {object} terms
1515       * @param {API.Term<string>} terms.of
1516       * @param {API.Term<string>} [terms.is]
1517       */
1518      ({ of, is }) => {
1519        return {
1520          match: { of, is },
1521          operator: /** @type {const} */ ('text/trim/end'),
1522        }
1523      }
1524    )
1525  
1526    static Length = Operator.for(
1527      /**
1528       * @param {object} terms
1529       * @param {API.Term<string>} terms.of
1530       * @param {API.Term<number>} [terms.is]
1531       */
1532      ({ of, is }) => {
1533        return {
1534          match: { of, is },
1535          operator: /** @type {const} */ ('text/length'),
1536        }
1537      }
1538    )
1539  }
1540  
1541  export class UTF8 {
1542    static ToText = Operator.for(
1543      /**
1544       * @param {object} terms
1545       * @param {API.Term<Uint8Array>} terms.of
1546       * @param {API.Term<string>} [terms.is]
1547       * @returns {API.SystemOperator}
1548       */
1549      ({ of, is }) => {
1550        return {
1551          match: { of, is },
1552          operator: /** @type {const} */ ('utf8/to/text'),
1553        }
1554      }
1555    )
1556  
1557    static FromText = Operator.for(
1558      /**
1559       * @param {object} terms
1560       * @param {API.Term<string>} terms.of
1561       * @param {API.Term<Uint8Array>} [terms.is]
1562       * @returns {API.SystemOperator}
1563       */
1564      ({ of, is }) => {
1565        return {
1566          match: { of, is },
1567          operator: /** @type {const} */ ('text/to/utf8'),
1568        }
1569      }
1570    )
1571  }
1572  
1573  export class Data {
1574    static same = Object.assign(
1575      /**
1576       * @template {API.Scalar} This
1577       * @template {API.Scalar} As
1578       * @param {object} terms
1579       * @param {API.Term<This>} terms.this
1580       * @param {API.Term<As>} [terms.as]
1581       * @returns {API.SystemOperator}
1582       */
1583      ({ this: of, as }) => {
1584        return /** @type {API.SystemOperator} */ ({
1585          match: { of, is: as },
1586          operator: /** @type {const} */ ('=='),
1587        })
1588      },
1589      {
1590        /**
1591         * @template {API.Scalar} This
1592         * @template {API.Scalar} As
1593         * @param {object} terms
1594         * @param {API.Term<This>} terms.this
1595         * @param {API.Term<As>} [terms.as]
1596         * @returns {API.Negation}
1597         */
1598        not: (terms) => ({ not: Data.same(terms) }),
1599      }
1600    )
1601  
1602    static not = Operator.for(
1603      /**
1604       * @param {object} terms
1605       * @param {API.Term<boolean>} terms.of
1606       * @param {API.Term<boolean>} terms.is
1607       * @returns {API.SystemOperator}
1608       */
1609      (terms) => {
1610        return /** @type {API.SystemOperator} */ ({
1611          match: terms,
1612          operator: /** @type {const} */ ('!'),
1613        })
1614      }
1615    )
1616    static ['!'] = this.not
1617  
1618    static greater = Operator.for(
1619      /**
1620       * @template {number|string} T
1621       * @param {object} terms
1622       * @param {API.Term<T>} terms.this
1623       * @param {API.Term<T>} terms.than
1624       * @returns {API.SystemOperator}
1625       */
1626      (terms) => {
1627        return {
1628          match: terms,
1629          operator: /** @type {const} */ ('>'),
1630        }
1631      }
1632    )
1633    static ['>'] = this.greater
1634  
1635    static greaterOrEqual = Operator.for(
1636      /**
1637       * @template {number|string} T
1638       * @param {object} terms
1639       * @param {API.Term<T>} terms.this
1640       * @param {API.Term<T>} terms.than
1641       * @returns {API.SystemOperator}
1642       */
1643      (terms) => {
1644        return {
1645          match: terms,
1646          operator: /** @type {const} */ ('>='),
1647        }
1648      }
1649    )
1650    static ['>='] = this.greaterOrEqual
1651  
1652    static less = Operator.for(
1653      /**
1654       * @template {number|string} T
1655       * @param {object} terms
1656       * @param {API.Term<T>} terms.this
1657       * @param {API.Term<T>} terms.than
1658       * @returns {API.SystemOperator}
1659       */
1660      (terms) => {
1661        return {
1662          match: terms,
1663          operator: /** @type {const} */ ('<'),
1664        }
1665      }
1666    )
1667    static ['<'] = this.less
1668  
1669    static lessOrEqual = Operator.for(
1670      /**
1671       * @template {number|string} T
1672       * @param {object} terms
1673       * @param {API.Term<T>} terms.this
1674       * @param {API.Term<T>} terms.than
1675       * @returns {API.SystemOperator}
1676       */
1677      (terms) => {
1678        return {
1679          match: terms,
1680          operator: /** @type {const} */ ('<='),
1681        }
1682      }
1683    )
1684  
1685    static ['<='] = this.lessOrEqual
1686  
1687    static Type = Operator.for(
1688      /**
1689       * @param {object} terms
1690       * @param {API.Term<API.Scalar>} terms.of
1691       * @param {API.Term<API.TypeName>|API.Term<string>} [terms.is]
1692       * @returns {API.SystemOperator}
1693       */
1694      ({ of, is }) => {
1695        return /** @type {API.SystemOperator} */ ({
1696          match: { of, is },
1697          operator: /** @type {const} */ ('data/type'),
1698        })
1699      }
1700    )
1701  
1702    static Reference = Operator.for(
1703      /**
1704       * @param {object} terms
1705       * @param {API.Term<any>} terms.of
1706       * @param {API.Term<API.Entity>} [terms.is]
1707       * @returns {{match: { of: API.Term, is?: API.Term<API.Entity> }, operator: 'data/refer' }}
1708       */
1709      ({ of, is }) => {
1710        return {
1711          match: { of, is },
1712          operator: /** @type {const} */ ('data/refer'),
1713        }
1714      }
1715    )
1716  
1717    static Fact = Operator.for(
1718      /**
1719       * @template {Record<string, API.Term> & {this?: API.Term<API.Entity>}} Terms
1720       * @param {Terms} terms
1721       * @returns {{match: Omit<Terms, 'this'> & { is?: API.Term<API.Entity> }, operator: 'data/refer' }}
1722       */
1723      ({ this: is, ...of }) => {
1724        return {
1725          match: { ...of, is },
1726          operator: 'data/refer',
1727        }
1728      }
1729    )
1730  }
1731  
1732  export class Math {
1733    static Sum = Operator.for(
1734      /**
1735       * @param {object} terms
1736       * @param {API.Term<API.Numeric>} terms.of
1737       * @param {API.Term<API.Numeric>} terms.with
1738       * @param {API.Term<API.Numeric>} [terms.is]
1739       * @returns {API.SystemOperator}
1740       */
1741      ({ of, with: by, is }) => {
1742        return /** @type {API.SystemOperator} */ ({
1743          match: { of, with: by, is },
1744          operator: /** @type {const} */ ('+'),
1745        })
1746      }
1747    )
1748    static ['+'] = this.Sum
1749  
1750    static Subtraction = Operator.for(
1751      /**
1752       * @param {object} terms
1753       * @param {API.Term<API.Numeric>} terms.of
1754       * @param {API.Term<API.Numeric>} terms.by
1755       * @param {API.Term<API.Numeric>} [terms.is]
1756       * @returns {API.SystemOperator}
1757       */
1758      (terms) => {
1759        return /** @type {API.SystemOperator} */ ({
1760          match: terms,
1761          operator: /** @type {const} */ ('-'),
1762        })
1763      }
1764    )
1765    static ['-'] = this.Subtraction
1766  
1767    static Multiplication = Operator.for(
1768      /**
1769       * @param {object} terms
1770       * @param {API.Term<API.Numeric>} terms.of
1771       * @param {API.Term<API.Numeric>} terms.by
1772       * @param {API.Term<API.Numeric>} [terms.is]
1773       * @returns {API.SystemOperator}
1774       */
1775      (terms) => {
1776        return /** @type {API.SystemOperator} */ ({
1777          match: terms,
1778          operator: /** @type {const} */ ('*'),
1779        })
1780      }
1781    )
1782    static ['*'] = this.Multiplication
1783  
1784    static Division = Operator.for(
1785      /**
1786       * @param {object} terms
1787       * @param {API.Term<API.Numeric>} terms.of
1788       * @param {API.Term<API.Numeric>} terms.by
1789       * @param {API.Term<API.Numeric>} [terms.is]
1790       * @returns {API.SystemOperator}
1791       */
1792      (terms) => {
1793        return /** @type {API.SystemOperator} */ ({
1794          match: terms,
1795          operator: /** @type {const} */ ('/'),
1796        })
1797      }
1798    )
1799    static ['/'] = this.Division
1800  
1801    static Modulo = Operator.for(
1802      /**
1803       * @param {object} terms
1804       * @param {API.Term<API.Numeric>} terms.of
1805       * @param {API.Term<API.Numeric>} terms.by
1806       * @param {API.Term<API.Numeric>} [terms.is]
1807       * @returns {API.SystemOperator}
1808       */
1809      (terms) => {
1810        return /** @type {API.SystemOperator} */ ({
1811          match: terms,
1812          operator: /** @type {const} */ ('%'),
1813        })
1814      }
1815    )
1816    static ['%'] = this.Modulo
1817  
1818    static Power = Operator.for(
1819      /**
1820       * @param {object} terms
1821       * @param {API.Term<API.Numeric>} terms.of
1822       * @param {API.Term<API.Numeric>} terms.exponent
1823       * @param {API.Term<API.Numeric>} [terms.is]
1824       * @returns {API.SystemOperator}
1825       */
1826      ({ of, exponent, is }) => {
1827        return /** @type {API.SystemOperator} */ ({
1828          match: { of, by: exponent, is },
1829          operator: /** @type {const} */ ('**'),
1830        })
1831      }
1832    )
1833    static ['**'] = this.Power
1834  
1835    static Absolute = Operator.for(
1836      /**
1837       * @param {object} terms
1838       * @param {API.Term<API.Numeric>} terms.of
1839       * @param {API.Term<API.Numeric>} [terms.is]
1840       * @returns {API.SystemOperator}
1841       */
1842      ({ of, is }) => {
1843        return /** @type {API.SystemOperator} */ ({
1844          match: { of, is },
1845          operator: /** @type {const} */ ('math/absolute'),
1846        })
1847      }
1848    )
1849  }
1850  
1851  const Same = Syntax.rule({ match: { this: $.this, as: $.this } })
1852  const NotSame = Syntax.rule({
1853    match: { this: $.this, as: $.as },
1854    when: {
1855      where: [{ not: Same.apply({ this: $.this, as: $.as }) }],
1856    },
1857  })
1858  
1859  export const same = Object.assign(
1860    /**
1861     * @template {API.Scalar} This
1862     * @template {API.Scalar} As
1863     * @param {{this: API.Term<This>, as: API.Term<As>}} terms
1864     */
1865    (terms) => new Match(Same, terms),
1866    {
1867      /**
1868       * @template {API.Scalar} This
1869       * @template {API.Scalar} As
1870       * @param {{this: API.Term<This>, as: API.Term<As>}} terms
1871       */
1872      not(terms) {
1873        return new Match(NotSame, terms)
1874      },
1875    }
1876  )
1877  
1878  /**
1879   * @template {API.RuleDescriptor} Schema
1880   * @param {Schema} schema
1881   * @returns {API.InferAttributes<Schema>}
1882   */
1883  function deriveAttributes(schema) {
1884    const match = /** @type {Record<string, API.Variable>} */ ({})
1885    for (const [key, type] of Object.entries(schema)) {
1886      match[key] = $[key]
1887    }
1888    match.this = $.this
1889  
1890    return /** @type {API.InferSchemaAttributes<Schema>} */ (match)
1891  }
1892  
1893  /**
1894   * @template {API.RuleDescriptor} Schema
1895   * @param {Schema} schema
1896   * @returns {API.InferSchemaAttributes<Schema> & {_: API.Variable}}
1897   */
1898  function deriveCells(schema) {
1899    return Object.assign(deriveAttributes(schema), { _: $._ })
1900  }
1901  
1902  /**
1903   * @template {API.FactSchema} Schema
1904   * @param {Schema} schema
1905   * @param {Partial<API.InferFactTerms<Schema>>} [input]
1906   * @returns {API.InferSchemaTerms<Schema> & { this: API.Term<API.Entity> }}
1907   */
1908  const completeTerms = (schema, input = {}) => {
1909    const terms = /** @type {Record<String, API.Term>} */ ({})
1910    for (const key of Object.keys(schema)) {
1911      const value = input[key]
1912      terms[key] = value === undefined ? $[Symbol(key)] : value
1913    }
1914  
1915    if (terms.this === undefined) {
1916      terms.this = $[Symbol('this')]
1917    }
1918  
1919    return /** @type {API.InferSchemaTerms<Schema> & { this: API.Term<API.Entity> }} */ (
1920      terms
1921    )
1922  }
1923  /**
1924   * @param {API.EveryView} source
1925   * @returns {Iterable<API.Conjunct|API.Recur>}
1926   */
1927  function* iterateConjuncts(source) {
1928    for (const member of source) {
1929      if (member === undefined) {
1930        continue
1931      } else if (Symbol.iterator in member) {
1932        for (const conjunct of member) {
1933          yield conjunct
1934        }
1935      } else {
1936        yield member
1937      }
1938    }
1939  }
1940  
1941  /**
1942   * @param {API.WhenView} source
1943   * @returns {Iterable<[string, API.EveryView]>}
1944   */
1945  function* iterateDisjuncts(source) {
1946    if (Array.isArray(source)) {
1947      yield ['where', source]
1948    } else {
1949      yield* Object.entries(source)
1950    }
1951  }