UNPKG

@dialog-db/query

Version:

Datalog query engine inspired by Datomic

1,285 lines (1,076 loc) 35.1 kB
import { ByteView } from 'multiformats' import { Task, Invocation } from './task.js' export type { ByteView, Task } /** * Generic reader interface that can be used to read `O` value form the * input `I` value. Reader may fail and error is denoted by `X` type. * * @template O - The output type of this reader * @template I - The input type of this reader. * @template X - The error type denotes failure reader may produce. */ export interface TryFrom< Type extends { Self: unknown Input: unknown }, > { tryFrom: (input: Type['Input']) => Result<Type['Self'], Error> } /** * Defines result type as per invocation spec * * @see https://github.com/ucan-wg/invocation/#6-result */ export type Result<T = unknown, X extends {} = {}> = Variant<{ ok: T error: X }> /** * Utility type for defining a [keyed union] type as in IPLD Schema. In practice * this just works around typescript limitation that requires discriminant field * on all variants. * * ```ts * type Result<T, X> = * | { ok: T } * | { error: X } * * const demo = (result: Result<string, Error>) => { * if (result.ok) { * // ^^^^^^^^^ Property 'ok' does not exist on type '{ error: Error; }` * } * } * ``` * * Using `Variant` type we can define same union type that works as expected: * * ```ts * type Result<T, X> = Variant<{ * ok: T * error: X * }> * * const demo = (result: Result<string, Error>) => { * if (result.ok) { * result.ok.toUpperCase() * } * } * ``` * * [keyed union]:https://ipld.io/docs/schemas/features/representation-strategies/#union-keyed-representation */ export type Variant<U extends Record<string, unknown>> = { [Key in keyof U]: { [K in Exclude<keyof U, Key>]?: never } & { [K in Key]: U[Key] } }[keyof U] export type Tagged<T> = { [Case in keyof T]: Exclude<keyof T, Case> extends never ? T : InferenceError<'It may only contain one key'> }[keyof T] /** * Utility type for including type errors in the typescript checking. It * defines impossible type (object with non-existent unique symbol field). * This type can be used in cases where typically `never` is used, but * where some error message would be useful. */ interface InferenceError<message> { [Marker]: never & message } export declare const Marker: unique symbol /** * A utility type to retain an unused type parameter `T`. * Similar to [phantom type parameters in Rust](https://doc.rust-lang.org/rust-by-example/generics/phantom.html). * * Capturing unused type parameters allows us to define "nominal types," which * TypeScript does not natively support. Nominal types in turn allow us to capture * semantics not represented in the actual type structure, without requiring us to define * new classes or pay additional runtime costs. * * For a concrete example, see {@link ByteView}, which extends the `Uint8Array` type to capture * type information about the structure of the data encoded into the array. */ export interface Phantom<T> { // This field can not be represented because field name is non-existent // unique symbol. But given that field is optional any object will valid // type constraint. [Marker]?: T } export type New<T, Type = Tagged<T>> = Tagged<T>[keyof Tagged<T>] & Phantom<Type> /** * Type representing a unit value. */ export interface Unit {} /** * Variable integer. */ export type Integer = New<{ Integer: number }> export type Float = New<{ Float: number }> /** * Type representing a raw bytes. */ export type Bytes = Uint8Array export type Null = null export type Reference = New<{ Reference: string }> export type Name = New<{ Name: string }> export type Position = New<{ Position: string }> /** * Type representing an IPLD link. */ export interface Link< Data extends {} | null = {} | null, Format extends number = number, Alg extends number = number, > { ['/']: ByteView<this> } /** * All the constants in the system represented as a union of the following types. * * We are likely to introduce uint32, int8, uint8 and etc but for now we have * chosen to keep things simple. */ export type Scalar = | null | boolean | bigint | Integer | Float | string | Bytes | Link /** * @deprecated Use `Scalar` instead. */ export type Constant = Scalar /** * Supported primitive types. Definition utilizes `Phantom` type to describe * the type for compile type inference and `Variant` type to describe it for * the runtime inference. * * Note we denote lexical order between types via `order` field. This is used * when comparing data across types. */ export type Type<T extends Scalar = Scalar> = Phantom<T> & Variant<{ Null: {} Boolean: {} Integer: {} Float: {} String: {} Bytes: {} Entity: {} Name: {} Position: {} Reference: {} Unknown: {} }> // /** // * Variable is placeholder for a value that will be ed against by the // * query engine. It is represented as an abstract `Reader` that will attempt // * to read arbitrary {@type Data} and return result with either `ok` of the // * `Type` or an `error`. // * // * Variables will be assigned unique `bindingKey` by a query engine that will // * be used as unique identifier for the variable. // */ // export interface Variable<T extends Constant = Constant> // extends TryFrom<{ Self: T; Input: Constant }> { // type: RowType // [VARIABLE_ID]?: VariableID // } /** * Variable is placeholder for a value that will be matched against by the * query engine. */ export interface Variable<T extends Scalar = Scalar> { ['?']: { type?: Type<T> id: VariableID } // is(term: Term): Conjunct // not(term: Term): Conjunct } export type VariableID = number /** * Term is either a constant or a {@link Variable}. Terms are used to describe * predicates of the query. */ export type Term<T extends Scalar = Scalar> = T | Variable<T> /** * Describes association between `entity`, `attribute`, `value` of the * {@link Fact}. Each component of the {@link _Relation} is a {@link Term} * that is either a constant or a {@link Variable}. * * Query engine during execution will attempt to match {@link _Relation} against * all facts in the database and unify {@link Variable}s across them to identify * all possible solutions. */ export type Pattern = readonly [ entity: Term<Entity>, attribute: Term<Attribute>, value: Term<Scalar>, ] export type Is = readonly [binding: Term<Scalar>, value: Term<Scalar>] export type Clause = Variant<{ // and clause And: Clause[] // or clause Or: Clause[] // negation Not: Clause // pattern match a fact Case: Pattern // rule application Rule: RuleApplication // assign bindings Is: Is Match: Formula }> export type InferCase< Methods extends Record<string, (input: any, context: any) => any> = {}, > = { [Case in keyof Methods]: { Case: Case Input: Parameters<Methods[Case]>[0] Context: Parameters<Methods[Case]>[1] Output: ReturnType<Methods[Case]> } } export type DispatchCase< Methods extends Record<string, (input: {}, context: {}) => {}> = {}, > = { <Case extends keyof Methods>( input: InferCase<Methods>[Case]['Input'], context: InferCase<Methods>[Case]['Context'] ): InferCase<Methods>[Case]['Output'] } export type Dispatch< Methods extends Record<string, (input: any, context: any) => any> = {}, > = DispatchCase<Methods> & { with<Extension extends Record<string, (input: any, context: any) => any>>( extension: Extension ): Dispatch<Methods & Extension> } export type Terms = Record<string, Term> | [Term, ...Term[]] | Term /** * Row is a named set of values which by default are {@link Term}s. It is meant * to represent a non-nested tuple with named members as opposed to indexed * members. */ export interface Row<T = Term> { [Key: string]: T } export type Numeric = Integer | Float /** * Describes operand of the operator. */ export type Operand = Scalar | Record<string, Scalar> | [Scalar, ...Scalar[]] type EphemeralEntity = | Term<Entity> | Record<string, Term> | [Term<Entity>, ...Term<Entity>[]] export type InferOperand<T, K = T> = K extends Scalar ? Term<T & Scalar> : K extends Array<infer U extends Scalar> ? Term<U>[] : { [Key in keyof K]: T[Key & keyof T] & K[Key] extends infer U extends Scalar ? Term<U> : never } export type TypeName = | 'null' | 'boolean' | 'string' | 'bigint' | 'integer' | 'float' | 'bytes' | 'reference' export type Tuple<T> = [T, ...T[]] export type InferYield<T> = T extends Iterable<infer U> ? U : never export type InferFormula< Operator extends string, Formula extends (input: In) => Iterable<Out>, In extends Operand = Parameters<Formula>[0], Out extends Operand = InferYield<ReturnType<Formula>>, > = readonly [ input: InferOperand<In>, operator: Operator, output?: InferOperand<Out>, ] import * as DataOperators from './formula/data.js' import * as TextOperators from './formula/text.js' import * as UTF8Operators from './formula/utf8.js' import * as MathOperators from './formula/math.js' export type Formula = | InferFormula<'==', typeof DataOperators.is> | InferFormula<'>', typeof DataOperators.greater> | InferFormula<'>=', typeof DataOperators.greaterOrEqual> | InferFormula<'<', typeof DataOperators.less> | InferFormula<'<=', typeof DataOperators.lessOrEqual> | InferFormula<'data/type', typeof DataOperators.type> | InferFormula<'data/refer', typeof DataOperators.refer> | InferFormula<'text/like', typeof TextOperators.like> | InferFormula<'text/length', typeof TextOperators.length> | InferFormula<'text/words', typeof TextOperators.words> | InferFormula<'text/lines', typeof TextOperators.lines> | InferFormula<'text/case/upper', typeof TextOperators.toUpperCase> | InferFormula<'text/case/lower', typeof TextOperators.toUpperCase> | InferFormula<'text/trim', typeof TextOperators.trim> | InferFormula<'text/trim/start', typeof TextOperators.trimStart> | InferFormula<'text/trim/end', typeof TextOperators.trimEnd> | InferFormula<'utf8/to/text', typeof UTF8Operators.fromUTF8> | InferFormula<'text/to/utf8', typeof UTF8Operators.toUTF8> | InferFormula<'text/includes', typeof TextOperators.includes> | InferFormula<'text/slice', typeof TextOperators.slice> | InferFormula<'text/concat', typeof TextOperators.concat> | InferFormula<'+', typeof MathOperators.addition> | InferFormula<'-', typeof MathOperators.subtraction> | InferFormula<'*', typeof MathOperators.multiplication> | InferFormula<'/', typeof MathOperators.division> | InferFormula<'%', typeof MathOperators.modulo> | InferFormula<'**', typeof MathOperators.power> | InferFormula<'math/absolute', typeof MathOperators.absolute> export type InferTerms<T extends Terms> = T extends Term<infer U> ? U : { [Key in keyof T]: T[Key] extends Term<infer U> ? U : never } export type Frame = Record<PropertyKey, Term> export type Entity = Link export type Attribute = string /** * An atomic fact in the database, associating an `entity` , `attribute` , * `value`. * * - `entity` - The first component is `entity` that specifies who or what the fact is about. * - `attribute` - Something that can be said about an `entity` . An attribute has a name, * e.g. "firstName" and a value type, e.g. string, and a cardinality. * - `value` - Something that does not change e.g. 42, "John", true. Fact relates * an `entity` to a particular `value` through an `attribute`.ich */ export interface Fact< T extends The = The, Of extends Entity = Entity, Is extends Scalar = Scalar, > { the: The of: Of is: Is } /** * An atomic {@link Fact} with a `cause` field providing a causal relationship * that acts like timestamp. */ export interface Datum< T extends The = The, Of extends Entity = Entity, Is extends Scalar = Scalar, > extends Fact<T, Of, Is> { cause: Entity } /** * Set of {@link Fact}s associating several attributes with the same new entity. * Each key represents an `attribute` and corresponding value represents it's * `value`. * * If value is an array of {@link Scalar}s then entity is associated each * value with a same attribute. * * If value is an `Instantiation` then entity is associated with a new entity * that is described by that `Instantiation`. * * If value is an array of `Instantiation`s then entity is associated with a * each `Instantiation` in the array with an attribute corresponding to the * key. */ export interface DataImport { [Key: string]: Scalar | Scalar[] | DataImport | DataImport[] } export interface FactsSelector { the?: Attribute of?: Entity is?: Scalar } export type Instruction = Variant<{ assert: Fact retract: Fact }> export interface Transaction extends Iterable<Instruction> {} export interface Transactor<Ok extends {} = {}> { transact(transaction: Transaction): Task<Ok, Error> } export interface Querier { select(selector?: FactsSelector): Task<Datum[], Error> } export type Proposition = Row<Variable> & { this?: Variable } export type Rule<Match extends Proposition = Proposition> = DeductiveRule<Match> export interface DeductiveRule<Match extends Proposition = Proposition> { readonly match: Match readonly when?: When<Conjunct | Recur> } export type Constraint = SelectForm | MatchRule | SystemOperator export interface Negation { not: Constraint operator?: undefined fact?: undefined rule?: undefined match?: undefined recur?: undefined } export type Conjunct = Constraint | Negation export type Recur<Match extends Proposition = Proposition> = { recur: RuleBindings<Match> operator?: undefined fact?: undefined rule?: undefined match?: undefined not?: undefined } export type Every<T extends Conjunct | Recur = Conjunct> = Iterable<T> export interface Some<T extends Conjunct | Recur = Conjunct> { readonly [Case: string]: Every<T> } export type When<T extends Conjunct | Recur = Conjunct> = Some<T> export type WhenBuilder<T extends RuleDescriptor> = | SomeBuilder<T> | EveryBuilder<T> export type SomeBuilder<T extends RuleDescriptor> = ( variables: InferSchemaAttributes<T> & { _: Variable<any> } ) => SomeView export type EveryBuilder<T extends RuleDescriptor> = ( variables: InferSchemaAttributes<T> & { _: Variable<any> } ) => EveryView export type ProjectionBuilder< T extends RuleDescriptor, Projection extends Selector, > = (variables: InferSchemaAttributes<T>) => Projection export type WhenView = EveryView | SomeView export type EveryView = ConjunctView[] export type ConjunctView = Conjunct | MatchView<unknown> | void export interface SomeView { [Case: string]: EveryView } export interface MatchRule<Match extends Proposition = Proposition> { readonly match: Partial<RuleBindings<Match>> readonly rule: Rule<Match> operator?: undefined fact?: undefined not?: undefined recur?: undefined } export interface Syntax { toJSON(): object toDebugString(): string plan(scope: Scope): EvaluationPlan } export interface SelectSyntax extends Syntax, SelectForm {} export interface RuleSyntax<Match extends Proposition = Proposition> extends Syntax, DeductiveRule<Match> { plan(scope: Scope): RulePlan } export interface RuleApplicationSyntax<Match extends Proposition = Proposition> extends Syntax, MatchRule<Match> { negate(): NegationSyntax plan(scope: Scope): RuleApplicationPlan<Match> prepare(): RuleApplicationPlan<Match> } export interface DeductiveRuleSyntax<Match extends Proposition = Proposition> extends Syntax, DeductiveRule<Match> { apply(terms?: RuleBindings<Match>): RuleApplicationSyntax<Match> } export interface RuleRecursionSyntax<Match extends Proposition = Proposition> extends Recur<Match> {} export interface NegationSyntax extends Syntax, Negation {} export interface SelectForm { match: Select /** * The `fact` field is reserved for the future use where it could be used to * specify data source or */ fact?: {} /** * The `rule` field can not be defined in order to be distinguishable * from the {@link RuleApplication} type. */ rule?: undefined /** * The `not` field can not be defined in order to be distinguishable * from the {@link Negation} type. */ not?: undefined operator?: undefined recur?: undefined } export type Select = SelectByAttribute | SelectByEntity | SelectByValue type SelectBy = { /** * {@link Term} representing a relation an entity `of` has with the value * `is`. In RDF notation this will correspond to a predicate. */ the?: Term<Attribute> /** * {@link Term} representing the entity / subject. */ of?: Term<Entity> /** * {@link Term} representing the value of the attribute on the entity (denoted * by `of`). In RDF notation this will correspond to an object. */ is?: Term<Scalar> /** * The `this` field is reserved for the future use where it could be used to * bind the merkle reference for this fact. */ this?: never } interface SelectByAttribute extends SelectBy { // Selection by attribute requires an attribute to be specified. the: Term<Attribute> } interface SelectByEntity extends SelectBy { // Selection by entity requires an entity to be specified. of: Term<Entity> } interface SelectByValue extends SelectBy { // Selection by value requires a value to be specified. is: Term<Scalar> } export interface FactSelection { select: Pattern rule?: undefined } export interface FormulaApplication { compute: string from: Pattern } export type InferFormulaApplication< Operator extends string, Formula extends (input: In) => Iterable<Out>, In extends Operand = Parameters<Formula>[0], Out extends Operand = InferYield<ReturnType<Formula>>, > = { compute: Operator from: InferOperand<In> to?: InferOperand<Out> } // export interface InductiveRule< // Match extends Conclusion = Conclusion, // Repeat extends Match = Match, // > { // match: Match // when: Conjuncts // repeat: Repeat // while: When // } export type SystemOperator = { [Operator in keyof SystemOperators]: MatchOperator< SystemOperators[Operator], Operator > }[keyof SystemOperators] export type MatchOperator<Formula = unknown, Identifier = Formula> = { readonly match: InferFormulaMatch<Formula> readonly operator: Identifier formula?: Formula fact?: undefined rule?: undefined not?: undefined recur?: undefined } export type InferFormulaMatch<F> = F extends (input: infer In) => Iterable<infer Out> ? FormulaMatch<In, Out> : never export type FormulaMatch<In, Out> = InferCells<In, 'of'> & Partial<InferCells<Out, 'is'>> export type InferCells<In, DefaultName extends string> = In extends Scalar ? { [key in DefaultName]: Term<In> } : In extends any[] ? { [key in DefaultName]: { [Key in keyof In]: In[Key] extends Scalar ? Term<In[Key]> : never } } : { [Key in keyof In]: In[Key] extends Scalar ? Term<In[Key]> : never } type SystemOperators = { '==': typeof DataOperators.is '>=': typeof DataOperators.greaterOrEqual '>': typeof DataOperators.greater '<': typeof DataOperators.less '<=': typeof DataOperators.lessOrEqual '!': typeof DataOperators.not 'data/type': typeof DataOperators.type 'data/refer': typeof DataOperators.refer 'text/like': typeof TextOperators.like 'text/length': typeof TextOperators.length 'text/words': typeof TextOperators.words 'text/lines': typeof TextOperators.lines 'text/case/upper': typeof TextOperators.toUpperCase 'text/case/lower': typeof TextOperators.toUpperCase 'text/trim': typeof TextOperators.trim 'text/trim/start': typeof TextOperators.trimStart 'text/trim/end': typeof TextOperators.trimEnd 'utf8/to/text': typeof UTF8Operators.fromUTF8 'text/to/utf8': typeof UTF8Operators.toUTF8 'text/includes': typeof TextOperators.includes 'text/slice': typeof TextOperators.slice 'text/concat': typeof TextOperators.concat '+': typeof MathOperators.addition '-': typeof MathOperators.subtraction '*': typeof MathOperators.multiplication '/': typeof MathOperators.division '%': typeof MathOperators.modulo '**': typeof MathOperators.power 'math/absolute': typeof MathOperators.absolute } export type RuleBindings<Case extends Proposition = Proposition> = { [Key in keyof Case]: Term<Scalar> } export interface RuleApplication<Match extends Proposition = Proposition> { // ⚠️ This is actually Partial<RuleBindings<Match>> but we still type it // without `Partial` because at the type level we have no good way of // omitting variables that could be ignored match: RuleBindings<Match> rule: Rule<Match> } export type InferRuleMatch<Case extends Proposition> = { [Key in keyof Case]: Case[Key] extends Variable<infer U> ? U extends any ? Term<Scalar> : Term<U> : never } export interface Variables extends Record<PropertyKey, Variable> {} export interface Bindings extends Record<PropertyKey, Term> {} /** * Selection describes set of (named) variables that query engine will attempt * to find values for that satisfy the query. */ // export interface Selector // extends Record<PropertyKey, Term | Term[] | Selector | Selector[]> {} export type Selector = AggregateSelector | NamedSelector /** * Where clause describes the conditions that must be satisfied for the query * to return a result. */ export type Where = Iterable<Clause> /** * Query that can be evaluated against the database. */ export type Query<Select extends Selector = Selector> = { select: Select where: Where } export type AggregateSelector = [Selector | Term] export interface NamedSelector extends Record<string, Selector | Term> {} export interface Variables extends Record<string, Term> {} export type Selection = Selector | Variable<Link<Bindings>> export interface Not { not: Constraint match?: void rule?: void } export type Combinator = Variant<{}> export type Confirmation = Variant<{ ok: Unit error: Error }> export type InferBindings<Selection extends Selector> = { [Key in keyof Selection]: Selection[Key] extends Term<infer T> ? T : Selection[Key] extends Term<infer T>[] ? T[] : Selection[Key] extends Selector[] ? InferBindings<Selection[Key][0]>[] : Selection[Key] extends Selector ? InferBindings<Selection[Key]> : never } export type InferTerm<T extends Term> = T extends Term<infer U> ? U : never export interface Analysis { dependencies: Set<VariableID> binds: Set<VariableID> cost: number } export interface Unplannable extends Error { error: this } export interface EvaluationPlan { evaluate(context: EvaluationContext): Task<MatchFrame[], EvaluationError> } /** * Represents a local variable references to a remote variables. This is n:1 * relation meaning multiple local variables may point to the same remote one * but local variable can point to at most one remote variable. */ export type Cursor = Map<Variable, Set<Variable>> /** * Represents set of bound variables. */ export type QueryBindings = Map<Variable, Scalar> export interface Scope { references: Cursor bindings: QueryBindings } export type Plan = Unplannable | EvaluationPlan export interface RulePlan extends EvaluationPlan { cost: number match: Proposition } export interface RuleApplicationPlan<Match extends Proposition> extends EvaluationPlan { cost: number toJSON(): object query(source: { from: Querier }): Task<MatchFrame[], Error> } export interface EvaluationContext { selection: MatchFrame[] source: Querier self: RulePlan recur: [MatchFrame, MatchFrame][] // Array of pairs [nextBindings, originalContext] for recursive processing } export interface Evaluator extends EvaluationContext { evaluate(context: EvaluationContext): Task<Bindings[], EvaluationError> } export interface EvaluationError extends Error {} export type $ = Variable<any> & Record<PropertyKey, Variable<any>> & { new (): $ (): $ name: Variable<string> length: Variable<number> prototype: Variable } export interface MatchFrame extends Map<Variable, Scalar> { parent?: MatchFrame } /** * Describes the effects that clause performs when evaluated. */ export interface Effects { /** * Query an underlying data source for facts. */ readonly query: readonly QueryEffect[] /** * Evaluate underlying clause in a loop potentially many times. */ readonly loop: readonly LoopEffect[] } /** * Describes looping effect, meaning that that clause with this effect * may be evaluated multiple times. In a future we may capture more details * about the loop. */ export interface LoopEffect {} export interface QueryEffect { select: Pattern } export type ObjectDescriptor = { [Key: string]: TypeDescriptor } export type ArrayDescriptor = [TypeDescriptor] & { Object?: undefined Rule?: undefined } export type UnknownDescriptor = { Unknown: {} } export type TypeDescriptor = | Scalar | ScalarConstructor | Type | ObjectDescriptor | ArrayDescriptor export type InferDescriptorType<T> = T extends null ? null : T extends { Null: {} } ? null : T extends BooleanConstructor ? boolean : T extends { Boolean: {} } ? boolean : T extends boolean ? T : T extends StringConstructor ? string : T extends { String: {} } ? string : T extends string ? T : T extends NumberConstructor ? Integer : T extends { Integer: {} } ? Integer : T extends { Float: {} } ? Float : T extends number ? T : T extends BigIntConstructor ? bigint : T extends bigint ? T : T extends Uint8ArrayConstructor ? Bytes : T extends { Bytes: {} } ? Bytes : T extends Uint8Array ? T : T extends ObjectConstructor ? Entity : T extends UnknownDescriptor ? Scalar : never export type ScalarConstructor = | BooleanConstructor | StringConstructor | NumberConstructor | BigIntConstructor | Uint8ArrayConstructor | ObjectConstructor export type ScalarDescriptor = Variant<{ Null: {} Boolean: {} String: {} Int32: {} Float32: {} Int64: {} Bytes: {} Reference: {} Entity: {} Unknown: {} }> & { Object?: undefined; Fact?: undefined; Scalar?: undefined } export type ModelDescriptor< Descriptor extends ObjectDescriptor = ObjectDescriptor, > = { Object: Descriptor } export type InferTypeTerms<T, U = T> = T extends Scalar ? Term<U extends Scalar ? U : never> : unknown extends T ? Term : InferEntityTerms<T> export type TypeTest<T> = T extends Scalar ? Box<T> : never export type Box<T> = { t: T } export type InferEntityTerms<T> = Partial< { this: Term<Entity> } & { [Key in keyof T]: InferTypeTerms<T[Key]> } > export type InferTypeVariables<T, U = T> = T extends Scalar ? Variable<U extends Scalar ? U : never> : unknown extends T ? Variable<any> : { this: Term<Entity> } & { [Key in keyof T]: InferTypeVariables<T[Key]> } export interface RuleDescriptor { [key: string]: ScalarConstructor | Type | Scalar } export interface FactSchema extends RuleDescriptor { this: ObjectConstructor } export type InferSchemaAttributes<Schema> = { [Key in keyof Schema]: Variable<InferDescriptorType<Schema[Key]>> } export type InferSchemaTerms<T> = { [Key in keyof T]: Term<InferDescriptorType<T[Key]>> } export type InferFact<Schema extends RuleDescriptor> = { [Key in keyof Schema]: InferDescriptorType<Schema[Key]> } export type InferRuleAssert<T extends RuleDescriptor> = { [Key in keyof T as T[Key] extends Scalar ? never : Key]: T[Key] extends ( Scalar ) ? undefined : InferDescriptorType<T[Key]> } export type ScalarTerms<T extends Scalar> = Term<T> | { this: Term<T> } export interface MatchView<Model = unknown> extends Iterable<Recur | Conjunct> {} export interface QueryView<Model> extends Iterable<Conjunct> { select(source: { from: Querier }): Invocation<Model[], Error> } export type EntityModel<T extends {} = {}> = { this: Entity } & T export type FactModel = { the?: The of?: EntityModel is?: Scalar | {} } export interface RuleApplicationView<View> extends RuleApplication, MatchView<View> { select(source: { from: Querier }): Invocation<View[], Error> } export type EntityView<Model> = Model & { this: Entity } export type TermTree = { [Key: string]: Term | TermTree } export type The = `${string}/${string}` export interface FactCells { the: Variable<string> of: Variable<Entity> is: Variable<Scalar> } export type Descriptor = null | boolean interface TextVariable extends Variable<string> { like(pattern: Term<string>): Constraint toUpperCase(is: Term<string>): Constraint toLowerCase(is: Term<string>): Constraint } export type InferFactTerms<T extends FactSchema> = { [Key in keyof Omit<T, 'this'>]: Term<InferDescriptorType<T[Key]>> } & { this?: Term<Entity> } export type InferAssert<Schema extends FactSchema> = InferFact< Omit<Schema, 'this'> > & { this?: Entity } export type InferClaimTerms<Schema extends FactSchema> = InferFactTerms<Schema> export type InferAttributes<Schema> = { [Key in keyof Schema]: Variable<InferDescriptorType<Schema[Key]>> } export interface Premise<The extends string, Schema extends FactSchema> { readonly the: The readonly attributes: InferAttributes<Schema & { this: ObjectDescriptor }> readonly schema: Schema } export interface Conclusion< Fact, The extends string, Schema extends FactSchema, > { assert(fact: InferAssert<Schema>): Fact } export interface Claim< Fact, The extends string, Schema extends FactSchema, Context extends RuleDescriptor, > extends Relation<Fact, The, Schema> { the: The attributes: InferSchemaAttributes<Schema> schema: Schema /** * Defines temporary variables made available in the {@link when} / * {@link where} builder methods so they can be used inside the rule body. */ with<Extension extends Exclude<RuleDescriptor, Schema & Context>>( extension: Extension ): Claim<Fact, The, Schema, Context & Extension> /** * Defines a rule that concludes fact corresponding to this premise whenever * all of the predicates returne by `derive` method are true. This is a * shortuct for {@link when} which is convinient in cases with a single * branch. */ where( derive: EveryBuilder<Schema & Context> ): Deduction<Fact, The, Schema, {}> /** * Defines a rule that deduces this fact whenever any of the branches are true. * Takes a `build` function that will be given set of variables corresponding * to the fact members which must return object where keys represent disjuncts * and values are arrays representing conjuncts for those disjuncts. In other * works each member of the returned object represent OR branches where each * branch is an AND joined predicates by passed variables. */ when(derive: SomeBuilder<Schema & Context>): Deduction<Fact, The, Schema, {}> map<View>(mapper: (fact: Fact) => View): Claim<View, The, Schema, Context> aggregate<State, View>( compressor: Aggregator<View, Fact, State> ): Aggregation<View, Fact, The, Schema> } export interface Aggregator<Output, Input, State> { open(): State merge(state: State, input: Input): State close(state: State): Output } export interface Aggregation<View, Fact, The, Schema extends FactSchema> { /** * Creates a predicate that matches this premise. This is just like * {@link match} except it requires passing all members explicitly, * this allows type checker to ensure that no members are left out by * accident. */ (terms?: InferFactTerms<Schema>): Aggregate<View> /** * Creates predicate that matches this premise. It may be passed terms for * the subset of the fact members. Omitted members are treated as `_` meaning * any value would satisfy them. */ match(terms?: Partial<InferFactTerms<Schema>>): Aggregate<View> /** * Creates negation (anti-join) that will omit all the facts that match * the premise with the given terms. */ not(terms: Partial<InferSchemaTerms<Schema>>): NegationPredicate /** * Creates an assertion for this the fact denoted by this premise, which can * be transacted in the DB. */ assert(fact: InferAssert<Schema>): Fact the: The schema: Schema } export interface Aggregate<View> extends Iterable<Recur | Conjunct> { query(source: { from: Querier }): Invocation<View, Error> } export interface NegationPredicate extends Iterable<Negation> {} /** * */ export interface Predicate<Fact, The extends string, Schema extends FactSchema> extends Iterable<Recur | Conjunct> { query(source: { from: Querier }): Invocation<Fact[], Error> } export interface Assertion extends Iterable<{ assert: Fact }> {} export type FactView< The extends string, Schema extends FactSchema, > = InferFact<Schema> & { the: The toJSON(): InferFact<Schema> & { the: The } } & Assertion & Retractable export interface Retractable { retract(): Iterable<{ retract: Fact }> } export interface Relation<Fact, The extends string, Schema extends FactSchema> { /** * Creates a predicate that matches this premise. This is just like * {@link match} except it requires passing all members explicitly, * this allows type checker to ensure that no members are left out by * accident. */ (terms?: InferFactTerms<Schema>): Predicate<Fact, The, Schema> /** * Creates predicate that matches this premise. It may be passed terms for * the subset of the fact members. Omitted members are treated as `_` meaning * any value would satisfy them. */ match(terms?: Partial<InferFactTerms<Schema>>): Predicate<Fact, The, Schema> /** * Creates negation (anti-join) that will omit all the facts that match * the premise with the given terms. */ not(terms: Partial<InferSchemaTerms<Schema>>): NegationPredicate /** * Creates an assertion for this the fact denoted by this premise, which can * be transacted in the DB. */ assert(fact: InferAssert<Schema>): Fact } export interface Deduction< Fact, The extends string, Schema extends FactSchema, Context extends RuleDescriptor, > extends Claim<Fact, The, Schema, Context> { inductive: Relation<Fact, The, Schema> /** * Creates an assertion for this the fact denoted by this premise, which can * be transacted in the DB. */ claim(fact: InferFactTerms<Schema>): Iterable<Conjunct> select<Terms extends Selector>( derive: ProjectionBuilder<Schema & Context, Terms> ): Projection<Schema, Terms> map<View>(mapper: (fact: Fact) => View): Deduction<View, The, Schema, Context> } export interface Projection<Schema extends FactSchema, Terms extends Selector> { (terms?: InferSchemaTerms<Schema>): SelectionPredicate<Terms> match(terms?: Partial<InferSchemaTerms<Schema>>): SelectionPredicate<Terms> } export interface SelectionPredicate<Terms extends Selector> extends Iterable<Recur | Conjunct> { query(source: { from: Querier }): Invocation<InferBindings<Terms>[], Error> }