From aab6b5057b5ef2db1d003e019b2ba0693a04e13b Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Mon, 4 Nov 2019 11:43:19 +0100 Subject: [PATCH] Data factory types fix #355 --- src/data-factory-internal.ts | 32 ++++++++--- src/data-factory-type.ts | 53 ++++++++++-------- src/formula.ts | 69 ++++++++++++----------- src/parse.ts | 7 ++- src/serialize.ts | 20 ++++--- src/store.ts | 105 +++++++++++++++++++++-------------- src/types.ts | 96 +++++++++++++++++++++++--------- src/utils.ts | 37 +++++++++++- src/wip.md | 8 ++- 9 files changed, 281 insertions(+), 146 deletions(-) diff --git a/src/data-factory-internal.ts b/src/data-factory-internal.ts index fc23feea8..e3290caed 100644 --- a/src/data-factory-internal.ts +++ b/src/data-factory-internal.ts @@ -9,8 +9,10 @@ import { PredicateType, ObjectType, GraphType, + TermType, + TFDataFactory, } from './types' -import { Feature, IdentityFactory } from './data-factory-type' +import { Feature, IdentityFactory, Indexable } from './data-factory-type' import { Node } from './index' export const defaultGraphURI = 'chrome:theSession' @@ -35,7 +37,7 @@ function defaultGraph(): NamedNode { * * Equivalent to {Term.hashString} */ -function id (term: Node) { +function id (term: Node): string | undefined { if (!term) { return term } @@ -47,13 +49,13 @@ function id (term: Node) { } switch (term.termType) { - case "NamedNode": + case TermType.NamedNode: return '<' + term.value + '>' - case "BlankNode": + case TermType.BlankNode: return '_:' + term.value - case "Literal": - return Literal.toNT(term) - case "Variable": + case TermType.Literal: + return Literal.toNT(term as Literal) + case TermType.Variable: return Variable.toString(term) default: return undefined @@ -118,7 +120,21 @@ function variable(name?: string): Variable { return new Variable(name) } -const CanonicalDataFactory: IdentityFactory = { +const CanonicalDataFactory: TFDataFactory< + NamedNode, + BlankNode, + Literal, + SubjectType, + PredicateType, + ObjectType, + GraphType, + // DefaultGraph is: + NamedNode | BlankNode, + Statement +> & IdentityFactory < + Statement, + NamedNode | BlankNode | Literal | Variable +> = { blankNode, defaultGraph, literal, diff --git a/src/data-factory-type.ts b/src/data-factory-type.ts index 36b176259..aa8161d28 100644 --- a/src/data-factory-type.ts +++ b/src/data-factory-type.ts @@ -1,4 +1,4 @@ -import { TFNamedNode, TFBlankNode, TFLiteral, TFQuad, TFTerm, TFDataFactory } from "./types" +import { TFNamedNode, TFBlankNode, TFLiteral, TFQuad, TFTerm, TFDataFactory, TFDefaultGraph, TFSubject, TFPredicate, TFObject, TFGraph } from "./types" /** * Defines a strict subset of the DataFactory as defined in the RDF/JS: Data model specification @@ -9,8 +9,28 @@ export interface DataFactory< NamedNode extends TFNamedNode = TFNamedNode, BlankNode extends TFBlankNode = TFBlankNode, Literal extends TFLiteral = TFLiteral, - FactoryTypes = NamedNode | TFBlankNode | Literal | TFQuad -> extends TFDataFactory { + Quad = TFQuad, + FactoryTypes = NamedNode | TFBlankNode | Literal | Quad, + Subject = TFSubject, + Predicate = TFPredicate, + Object = TFObject, + Graph = TFGraph, + DefaultGraph = NamedNode | BlankNode, +> extends TFDataFactory< + NamedNode, + BlankNode, + Literal, + Subject, + Predicate, + Object, + Graph, + DefaultGraph, + Quad +> { + /** + * BlankNode index + * @private + */ bnIndex?: number supports: SupportTable @@ -19,30 +39,20 @@ export interface DataFactory< literal(value: unknown): Literal - defaultGraph(): NamedNode | BlankNode - - quad( - subject: NamedNode | BlankNode, - predicate: NamedNode, - object: NamedNode | BlankNode | Literal, - graph?: NamedNode - ): TFQuad - - isQuad(obj: any): obj is TFQuad - - fromTerm(original: Literal | TFTerm): TFTerm - - fromQuad(original: TFQuad): TFQuad + isQuad(obj: any): obj is Quad equals(a: Comparable, b: Comparable): boolean toNQ(term: FactoryTypes): string } +export type TFIDFactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | TFQuad + export interface IdentityFactory< + Quad = TFQuad, + IDFactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | Quad, IndexType = Indexable, - FactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | TFQuad -> extends DataFactory { +> { /** * Generates a unique session-idempotent identifier for the given object. * @@ -51,8 +61,7 @@ export interface IdentityFactory< * * @return {Indexable} A unique value which must also be a valid JS object key type. */ - id(obj: FactoryTypes): IndexType | unknown - + id(obj: IDFactoryTypes): IndexType } /** @@ -65,7 +74,7 @@ export interface IdentityFactory< export interface ReversibleIdentityFactory< IndexType = Indexable, FactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | TFQuad -> extends IdentityFactory { +> extends IdentityFactory { fromId(id: IndexType): FactoryTypes; } diff --git a/src/formula.ts b/src/formula.ts index ebb15a24a..21917f961 100644 --- a/src/formula.ts +++ b/src/formula.ts @@ -8,12 +8,10 @@ import Namespace from './namespace' import Node from './node-internal' import Serializer from './serialize' import Statement from './statement' -import { appliedFactoryMethods, arrayToStatements, isTFStatement, uriCreator } from './utils' +import { appliedFactoryMethods, arrayToStatements, isTFStatement, uriCreator, isStatement } from './utils' import { TFTerm, - ValueType, TFPredicate, - TFDataFactory, Bindings, TFNamedNode, TFSubject, @@ -22,10 +20,12 @@ import { TFQuad, TermType, TFBlankNode, + TFDataFactory, + TFLiteral, } from './types' import Variable from './variable' import Literal from './literal' -import { IdentityFactory, Indexable } from './data-factory-type' +import { IdentityFactory, Indexable, DataFactory, TFIDFactoryTypes } from './data-factory-type' import IndexedFormula from './store' import Fetcher from './fetcher' @@ -33,8 +33,8 @@ export function isFormula(value: T | TFTerm): value is Formula { return (value as Node).termType === TermType.Graph } -interface FormulaOpts { - rdfFactory?: IdentityFactory +export interface FormulaOpts { + rdfFactory?: IdentityFactory & DataFactory } interface SeedsMap { @@ -53,7 +53,7 @@ export default class Formula extends Node { /** * The stored statements */ - statements: Statement[]; + statements: TFQuad[]; /** * The additional constraints @@ -70,7 +70,7 @@ export default class Formula extends Node { optional: ReadonlyArray /** The factory used to generate statements and terms */ - rdfFactory: IdentityFactory + rdfFactory: IdentityFactory & (TFDataFactory | DataFactory) /** * Initializes this formula @@ -82,7 +82,7 @@ export default class Formula extends Node { * @param opts.rdfFactory - The rdf factory that should be used by the store */ constructor( - statements?: Statement[], + statements?: TFQuad[], constraints?: ReadonlyArray, initBindings?: { [id: string]: Node; @@ -123,7 +123,7 @@ export default class Formula extends Node { object: TFObject, graph?: TFGraph ): number { - return (this.statements as Statement[]) + return (this.statements as TFQuad[]) .push(this.rdfFactory.quad(subject, predicate, object, graph)) } @@ -230,7 +230,7 @@ export default class Formula extends Node { p?: TFPredicate | null, o?: TFObject | null, g?: TFGraph | null - ): Statement | undefined { + ): TFQuad | undefined { var x = this.statementsMatching(s, p, o, g) if (!x || x.length === 0) { return undefined @@ -243,7 +243,7 @@ export default class Formula extends Node { * * Falls back to the rdflib hashString implementation if the given factory doesn't support id. */ - id (term: TFTerm): Indexable { + id (term: TFIDFactoryTypes): Indexable { return this.rdfFactory.id(term) } @@ -262,7 +262,7 @@ export default class Formula extends Node { p?: TFPredicate | null, o?: TFObject | null, g?: TFGraph | null - ): Statement[] { + ): TFQuad[] { let found = this.statements.filter(st => (!s || s.equals(st.subject)) && (!p || p.equals(st.predicate)) && @@ -340,7 +340,7 @@ export default class Formula extends Node { o?: TFObject | null, g?: TFGraph | null ): TFTerm[] { - var elt: Statement, i: number, l: number, m: number, q: number + var elt: TFQuad, i: number, l: number, m: number, q: number var len: number, len1: number, len2: number, len3: number var results: TFTerm[] = [] var sts = this.statementsMatching(s, p, o, g) @@ -362,7 +362,7 @@ export default class Formula extends Node { } else if (g == null) { for (q = 0, len3 = sts.length; q < len3; q++) { elt = sts[q] - results.push(elt.why) + results.push(elt.graph) } } return results @@ -392,7 +392,7 @@ export default class Formula extends Node { findMembersNT( thisClass: Node ): { - [uri: string]: Statement; + [uri: string]: TFQuad; } { var i: number var l: number @@ -406,11 +406,11 @@ export default class Formula extends Node { var pred: TFTerm var q: number var ref - var ref1: Statement[] + var ref1: TFQuad[] var ref2: TFTerm[] - var ref3: Statement[] + var ref3: TFQuad[] var ref4: TFTerm[] - var ref5: Statement[] + var ref5: TFQuad[] var seeds var st var t @@ -544,7 +544,7 @@ export default class Formula extends Node { var types rdftype = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' types = [] - ref = this.statementsMatching(subject, void 0, void 0) + ref = this.statementsMatching(subject, void 0, void 0) as Statement[] for (i = 0, len = ref.length; i < len; i++) { st = ref[i] if (st.predicate.value === rdftype) { @@ -557,7 +557,7 @@ export default class Formula extends Node { } } } - ref2 = this.statementsMatching(void 0, void 0, subject) + ref2 = this.statementsMatching(void 0, void 0, subject) as Statement[] for (m = 0, len2 = ref2.length; m < len2; m++) { st = ref2[m] ref3 = this.each(st.predicate, this.sym('http://www.w3.org/2000/01/rdf-schema#range')) @@ -648,7 +648,7 @@ export default class Formula extends Node { * This will only parse the strings generated by the vaious toNT() methods. * @param str A string representation */ -fromNT(str: string): TFTerm { + fromNT(str: string): TFLiteral | TFBlankNode | TFNamedNode | Variable { var dt: TFNamedNode | undefined, k: number, lang: string | undefined switch (str[0]) { case '<': @@ -705,7 +705,7 @@ fromNT(str: string): TFTerm { } return true } else if (isTFStatement(s)) { - return this.holds(s.subject, s.predicate, s.object, s.why) + return this.holds(s.subject, s.predicate, s.object, s.graph) } } @@ -717,8 +717,8 @@ fromNT(str: string): TFTerm { * Gets whether this formula holds the specified statement * @param st A statement */ - holdsStatement(st: Statement): boolean { - return this.holds(st.subject, st.predicate, st.object, st.why) + holdsStatement(st: TFQuad): boolean { + return this.holds(st.subject, st.predicate, st.object, st.graph) } /** @@ -729,6 +729,7 @@ fromNT(str: string): TFTerm { */ list(values: [], context: IndexedFormula): Collection | TFBlankNode { if (context.rdfFactory.supports["COLLECTIONS"]) { + //@ts-ignore if a rdfFactory supports collections, the collection() method should work const collection = context.rdfFactory.collection() values.forEach(function (val) { collection.append(val) @@ -751,7 +752,7 @@ fromNT(str: string): TFTerm { literal( val: string, lang?: string, - dt?: NamedNode + dt?: TFNamedNode ): Literal { return new Literal('' + val, lang, dt) } @@ -763,9 +764,9 @@ fromNT(str: string): TFTerm { * todo: explain why it is important to go through NT */ NTtoURI(t: { - [uri: string]: any; + [uri: string]: string; }): { - [uri: string]: any; + [uri: string]: string; }{ var k, v var uris = {} @@ -781,13 +782,13 @@ fromNT(str: string): TFTerm { /** * Serializes this formula - * @param base The base string - * @param contentType The content type of the syntax to use - * @param provenance The provenance URI + * @param base - The base string + * @param contentType - The content type of the syntax to use + * @param provenance - The provenance URI */ serialize(base: string, contentType: string, provenance: string): string { var documentString - var sts: Statement[] + var sts: TFQuad[] var sz sz = Serializer(this) sz.suggestNamespaces(this.namespaces) @@ -817,12 +818,14 @@ fromNT(str: string): TFTerm { * Gets a new formula with the substituting bindings applied * @param bindings - The bindings to substitute */ + //@ts-ignore signature not compatible with Node substitute(bindings: Bindings): Formula { var statementsCopy = this.statements.map(function (ea) { - return ea.substitute(bindings) + return (ea as Statement).substitute(bindings) }) console.log('Formula subs statmnts:' + statementsCopy) var y = new Formula() + // This will throw an error, since this.add() can't handle y.add(statementsCopy) console.log('indexed-form subs formula:' + y) return y diff --git a/src/parse.ts b/src/parse.ts index db6cdbac9..b834f6d15 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -7,7 +7,7 @@ import RDFParser from './rdfxmlparser' import sparqlUpdateParser from './patch-parser' import * as Util from './util' import Formula from './formula'; -import { TFQuad, ContentType } from './types'; +import { TFQuad, ContentType, ContentTypes } from './types'; /** * Parse a string and put the result into the graph kb. @@ -24,11 +24,11 @@ export default function parse ( str: string, kb: Formula, base: string, - contentType: string | ContentType, + contentType: string | ContentTypes, callback?: (error: any, kb: Formula | null) => void ) { contentType = contentType || ContentType.turtle - contentType = contentType.split(';')[0] as ContentType + contentType = contentType.split(';')[0] as ContentTypes try { if (contentType === ContentType.n3 || contentType === ContentType.turtle) { var p = N3Parser(kb, kb, base, base, null, null, '', null) @@ -92,6 +92,7 @@ export default function parse ( callback(e, kb) } else { let e2 = new Error('' + e + ' while trying to parse <' + base + '> as ' + contentType) + //@ts-ignore .cause is not a default error property e2.cause = e throw e2 } diff --git a/src/serialize.ts b/src/serialize.ts index f283cf7f9..7221169e7 100644 --- a/src/serialize.ts +++ b/src/serialize.ts @@ -1,34 +1,36 @@ import * as convert from './convert' import Serializer from './serializer' -import { ContentType } from './types' +import { ContentType, ContentTypes, TFNamedNode, TFBlankNode } from './types' import IndexedFormula from './store' +import { Formula } from './index' /** * Serialize to the appropriate format */ export default function serialize ( - target, + /** The graph or nodes that should be serialized */ + target: Formula | TFNamedNode | TFBlankNode, /** The store */ - kb: IndexedFormula, + kb?: IndexedFormula, base?, /** * The mime type. * Defaults to Turtle. */ - contentType?: ContentType, - callback?: (err: Error, result?: any ) => void, + contentType?: string | ContentTypes, + callback?: (err?: Error | null, result?: string ) => void, options? ) { - base = base || target.uri + base = base || target.value options = options || {} contentType = contentType || ContentType.turtle // text/n3 if complex? var documentString: string | null = null try { var sz = Serializer(kb) if (options.flags) sz.setFlags(options.flags) - var newSts = kb.statementsMatching(undefined, undefined, undefined, target) + var newSts = kb!.statementsMatching(undefined, undefined, undefined, target) var n3String: string - sz.suggestNamespaces(kb.namespaces) + sz.suggestNamespaces(kb!.namespaces) sz.setBase(base) switch (contentType) { case ContentType.rdfxml: @@ -71,7 +73,7 @@ export default function serialize ( throw err // Don't hide problems from caller in sync mode } - function executeCallback (err, result) { + function executeCallback (err?: Error | null, result?: string) { if (callback) { callback(err, result) return diff --git a/src/store.ts b/src/store.ts index 8a8e6865e..d785cdb52 100644 --- a/src/store.ts +++ b/src/store.ts @@ -19,8 +19,8 @@ import ClassOrder from './class-order' import { defaultGraphURI } from './data-factory-internal' -import Formula from './formula' -import { ArrayIndexOf, isTFStatement, isStore, uriCreator } from './utils' +import Formula, { FormulaOpts } from './formula' +import { ArrayIndexOf, isTFStatement, isStore, uriCreator, isTFSubject, isTFPredicate, isTFObject, isTFGraph } from './utils' import Node from './node' import Variable from './variable' import { Query, indexedFormulaQuery } from './query' @@ -60,9 +60,16 @@ function handleIFP (formula, subj, pred, obj) { return true } // handleIFP -function handleRDFType (formula, subj, pred, obj, why) { +function handleRDFType ( + formula: IndexedFormula, + subj: SubjectType, + pred: PredicateType, + obj: ObjectType, + why: GraphType +) { + //@ts-ignore this method does not seem to exist in this library if (formula.typeCallback) { - formula.typeCallback(formula, obj, why) + (formula as any).typeCallback(formula, obj, why) } var x = formula.classActions[formula.id(obj)] @@ -89,8 +96,6 @@ export default class IndexedFormula extends Formula { // IN future - allow pass */ namespaces: {[key: string]: string} - handleRDFType?: () => any - /** Array of functions to call when adding { s type X } */ classActions: Function[] /** Array of functions to call when getting statement with {s X o} */ @@ -116,6 +121,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass TFQuad[] ] features: FeaturesType + static handleRDFType: Function /** * Creates a new formula @@ -124,7 +130,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @param opts * @param opts.rdfFactory - The data factory that should be used by the store */ - constructor (features?: FeaturesType, opts?: { rdfFactory: TFDataFactory}) { + constructor (features?: FeaturesType, opts?: FormulaOpts) { super(undefined, undefined, undefined, undefined, opts) this.propertyActions = [] @@ -163,6 +169,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * Gets this graph with the bindings substituted * @param bindings The bindings */ + //@ts-ignore different from signature in Formula substitute(bindings: Bindings): IndexedFormula { var statementsCopy = this.statements.map(function (ea) { return ea.substitute(bindings) @@ -312,33 +319,35 @@ export default class IndexedFormula extends Formula { // IN future - allow pass /** * Adds a triple (quad) to the store. * - * @param subject - The thing about which the fact a relationship is asserted - * @param predicate - The relationship which is asserted - * @param object - The object of the relationship, e.g. another thing or avalue + * @param subj - The thing about which the fact a relationship is asserted. + * Also accepts a statement or an array of Statements. + * @param pred - The relationship which is asserted + * @param obj - The object of the relationship, e.g. another thing or avalue * @param why - The document in which the triple (S,P,O) was or will be stored on the web - * @returns The statement added to the store + * @returns The statement added to the store, or the store */ + //@ts-ignore differs from signature in Formula add ( - subj: TFSubject, - pred: TFPredicate, - obj: TFObject, + subj: TFSubject | Statement | TFQuad[], + pred?: TFPredicate, + obj?: TFObject, why?: TFGraph - ): Statement | null { - var i + ): TFQuad | null | IndexedFormula { + var i: number if (arguments.length === 1) { if (subj instanceof Array) { for (i = 0; i < subj.length; i++) { this.add(subj[i]) } } else if (isTFStatement(subj)) { - this.add(subj.subject, subj.predicate, subj.object, subj.why) + this.add(subj.subject, subj.predicate, subj.object, subj.graph) } else if (isStore(subj)) { this.add(subj.statements) } return this } - var actions - var st + var actions: Function[] + var st: TFQuad if (!why) { // system generated why = this.fetcher ? this.fetcher.appNode : this.defaultGraph() @@ -349,7 +358,21 @@ export default class IndexedFormula extends Formula { // IN future - allow pass pred = Node.fromValue(pred) obj = Node.fromValue(obj) why = Node.fromValue(why) + if (!isTFSubject(subj)) { + throw "Subject is not a subject type" + } + if (!isTFPredicate(pred)) { + throw "Predicate is not a predicate type" + } + if (!isTFObject(obj)) { + throw "Predicate is not a predicate type" + } + if (!isTFGraph(why)) { + throw "Why is not a graph type" + } + //@ts-ignore This is not used internally if (this.predicateCallback) { + //@ts-ignore This is not used internally this.predicateCallback(this, pred, why) } // Action return true if the statement does not need to be added @@ -371,10 +394,10 @@ export default class IndexedFormula extends Formula { // IN future - allow pass // Don't put it in the store // still return this statement for owl:sameAs input var hash = [ - this.id(this.canon(subj)), + this.id(this.canon(subj as TFSubject)), predHash, - this.id(this.canon(obj)), - this.id(this.canon(why)) + this.id(this.canon(obj as TFObject)), + this.id(this.canon(why as TFGraph)) ] st = this.rdfFactory.quad(subj, pred, obj, why) for (i = 0; i < 4; i++) { @@ -714,7 +737,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * Removes one or multiple statement(s) from this formula * @param st - A Statement or array of Statements to remove */ - remove(st: Statement | Statement[]): IndexedFormula { + remove(st: TFQuad | TFQuad[]): IndexedFormula { if (st instanceof Array) { for (var i = 0; i < st.length; i++) { this.remove(st[i]) @@ -737,7 +760,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @param doc - The document / graph */ removeDocument(doc: TFGraph): IndexedFormula { - var sts = this.statementsMatching(undefined, undefined, undefined, doc).slice() // Take a copy as this is the actual index + var sts: TFQuad[] = this.statementsMatching(undefined, undefined, undefined, doc).slice() // Take a copy as this is the actual index for (var i = 0; i < sts.length; i++) { this.removeStatement(sts[i]) } @@ -779,13 +802,13 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @param graph The graph that contains the statement */ removeMatches( - subject?: SubjectType | null, - predicate?: PredicateType | null, - object?: ObjectType | null, - graph?: GraphType | null + subject?: TFSubject | null, + predicate?: TFPredicate | null, + object?: TFObject | null, + graph?: TFGraph | null ): IndexedFormula { this.removeStatements( - this.statementsMatching(subject, predicate, object,why) + this.statementsMatching(subject, predicate, object, graph) ) return this } @@ -797,9 +820,9 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * Make sure you only use this for these. * Otherwise, you should use remove() above. */ - removeStatement(st: Statement): IndexedFormula { + removeStatement(st: TFQuad): IndexedFormula { // log.debug("entering remove w/ st=" + st) - var term = [ st.subject, st.predicate, st.object, st.why ] + var term = [ st.subject, st.predicate, st.object, st.graph ] for (var p = 0; p < 4; p++) { var c = this.canon(term[p]) var h = this.id(c) @@ -817,7 +840,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * Removes statements * @param sts The statements to remove */ - removeStatements(sts: ReadonlyArray): IndexedFormula { + removeStatements(sts: ReadonlyArray): IndexedFormula { for (var i = 0; i < sts.length; i++) { this.remove(sts[i]) } @@ -827,7 +850,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass /** * Replace big with small, obsoleted with obsoleting. */ - replaceWith (big, small) { + replaceWith (big: TFTerm, small: TFTerm) { // log.debug("Replacing "+big+" with "+small) // this.id(@@ var oldhash = this.id(big) var newhash = this.id(small) @@ -849,7 +872,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass moveIndex(this.index[i]) } this.redirections[oldhash] = small - if (big.uri) { + if (big.value) { // @@JAMBO: must update redirections,aliases from sub-items, too. if (!this.aliases[newhash]) { this.aliases[newhash] = [] @@ -861,7 +884,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass this.aliases[newhash].push(this.aliases[oldhash][i]) } } - this.add(small, this.sym('http://www.w3.org/2007/ont/link#uri'), big.uri) + this.add(small, this.sym('http://www.w3.org/2007/ont/link#uri'), big.value) // If two things are equal, and one is requested, we should request the other. if (this.fetcher) { this.fetcher.nowKnownAs(big, small) @@ -898,10 +921,10 @@ export default class IndexedFormula extends Formula { // IN future - allow pass var y1 = this.canon(y) // alert('y1='+y1); //@@ if (!y1) return false - return (x1.uri === y1.uri) + return (x1.value === y1.value) } - setPrefixForURI (prefix, nsuri) { + setPrefixForURI (prefix: string, nsuri: string): void { // TODO: This is a hack for our own issues, which ought to be fixed // post-release // See http://dig.csail.mit.edu/cgi-bin/roundup.cgi/$rdf/issue227 @@ -924,14 +947,14 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @param justOne - flag - stop when found one rather than get all of them? * @returns An array of nodes which match the wildcard position */ - + //@ts-ignore incompatible with extended signature from Formula statementsMatching ( subj?: TFSubject | null, pred?: TFPredicate | null, obj?: TFObject | null, why?: TFGraph | null, justOne?: boolean - ): Statement[] { + ): TFQuad[] { // log.debug("Matching {"+subj+" "+pred+" "+obj+"}") var pat = [ subj, pred, obj, why ] var pattern: TFTerm[] = [] @@ -1011,8 +1034,8 @@ export default class IndexedFormula extends Formula { // IN future - allow pass uris(term: TFNamedNode): string[] { var cterm = this.canon(term) var terms = this.aliases[this.id(cterm)] - if (!cterm.uri) return [] - var res = [ cterm.uri ] + if (!cterm.value) return [] + var res = [ cterm.value ] if (terms) { for (var i = 0; i < terms.length; i++) { res.push(terms[i].uri) diff --git a/src/types.ts b/src/types.ts index ff562f30c..d122e12b9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,6 +6,7 @@ import Literal from './literal'; import NamedNode from './named-node'; import { TFNamedNode } from './types'; import DefaultGraph from './default-graph'; +import { SupportTable } from './data-factory-type'; /** * Types that support both Enums (for typescript) and regular strings @@ -29,21 +30,53 @@ export enum TermType { } /** A valid mime type header */ -export enum ContentType { - rdfxml = "application/rdf+xml", - turtle = "text/turtle", - turtleLegacy = "application/x-turtle", - n3 = "text/n3", - n3Legacy = "application/n3", - nTriples = "application/n-triples", - nQuads = "application/n-quads", - nQuadsAlt = "application/nquads", - jsonld = "application/ld+json", - xhtml = "application/xhtml+xml", - html = "text/html", - sparqlupdate = "application/sparql-update", +// export const enum ContentType { +// rdfxml = "application/rdf+xml", +// turtle = "text/turtle", +// turtleLegacy = "application/x-turtle", +// n3 = "text/n3", +// n3Legacy = "application/n3", +// nTriples = "application/n-triples", +// nQuads = "application/n-quads", +// nQuadsAlt = "application/nquads", +// jsonld = "application/ld+json", +// xhtml = "application/xhtml+xml", +// html = "text/html", +// sparqlupdate = "application/sparql-update", +// } + +/** A valid mime type header */ +export const ContentType = { + rdfxml: "application/rdf+xml", + turtle: "text/turtle", + turtleLegacy: "application/x-turtle", + n3: "text/n3", + n3Legacy: "application/n3", + nTriples: "application/n-triples", + nQuads: "application/n-quads", + nQuadsAlt: "application/nquads", + jsonld: "application/ld+json", + xhtml: "application/xhtml+xml", + html: "text/html", + sparqlupdate: "application/sparql-update", } +export type ContentTypes = + "application/rdf+xml" | + "text/turtle" | + "application/x-turtle" | + "application/x-turtle" | + "text/n3" | + "application/n3" | + "application/n-triples" | + "application/n-quads" | + "application/nquads" | + "application/ld+json" | + "application/xhtml+xml" | + "text/html" | + "application/sparql-update" + + /** A type for values that serves as inputs */ export type ValueType = TFTerm | Node | Date | string | number | boolean | undefined | null | Collection; @@ -168,6 +201,12 @@ export interface TFDataFactory< DFNamedNode extends TFNamedNode = TFNamedNode, DFBlankNode extends TFBlankNode = TFBlankNode, DFLiteral extends TFLiteral = TFLiteral, + DFSubject = TFSubject, + DFPredicate = TFPredicate, + DFObject = TFObject, + DFGraph = TFGraph, + DFDefaultGraph = TFDefaultGraph, + DFQuad = TFQuad, > { /** Returns a new instance of NamedNode. */ namedNode: (value: string) => DFNamedNode, @@ -186,29 +225,34 @@ export interface TFDataFactory< variable?: (value: string) => TFVariable, /** * Returns an instance of DefaultGraph. - * Note: This differs from the TF spec! */ - defaultGraph: () => TFDefaultGraph | DFNamedNode | DFBlankNode, + defaultGraph: () => DFDefaultGraph, /** * Returns a new instance of the specific Term subclass given by original.termType * (e.g., NamedNode, BlankNode, Literal, etc.), * such that newObject.equals(original) returns true. + * Not implemented in RDFJS, so optional. */ - fromTerm: (original: TFTerm) => TFTerm + fromTerm?: (original: TFTerm) => TFTerm /** * Returns a new instance of Quad, such that newObject.equals(original) returns true. + * Not implemented in RDFJS, so optional. */ - fromQuad: (original: TFQuad) => TFQuad + fromQuad?: (original: DFQuad) => DFQuad /** * Returns a new instance of Quad. * If graph is undefined or null it MUST set graph to a DefaultGraph. */ quad: ( - subject: TFSubject, - predicate: TFPredicate, - object: TFObject, - graph?: TFGraph - ) => TFQuad + subject: DFSubject, + predicate: DFPredicate, + object: DFObject, + graph?: DFGraph, + ) => DFQuad + /** + * This does not exist on the original RDF/JS spec + */ + supports: SupportTable } export interface Bindings { @@ -216,13 +260,13 @@ export interface Bindings { } /** A RDF/JS taskforce Subject */ -export type TFSubject = TFNamedNode | TFBlankNode // | TFVariable +export type TFSubject = TFNamedNode | TFBlankNode | TFVariable /** A RDF/JS taskforce Predicate */ -export type TFPredicate = TFNamedNode // | TFVariable +export type TFPredicate = TFNamedNode | TFVariable /** A RDF/JS taskforce Object */ -export type TFObject = TFNamedNode | TFBlankNode | TFLiteral // | TFVariable +export type TFObject = TFNamedNode | TFBlankNode | TFLiteral | TFVariable /** A RDF/JS taskforce Graph */ -export type TFGraph = TFNamedNode | TFDefaultGraph | TFBlankNode // | TFVariable +export type TFGraph = TFNamedNode | TFDefaultGraph | TFBlankNode | TFVariable /** All the types that a .fromValue() method might return */ export type FromValueReturns = TFTerm | undefined | null | Collection diff --git a/src/utils.ts b/src/utils.ts index 8ea58de4c..74a9c068e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,7 @@ import Fetcher from './fetcher'; -import { TFDataFactory, TFTerm, TFQuad, TFNamedNode, TFSubject, TermType, TFLiteral } from './types'; -import { IndexedFormula, NamedNode, BlankNode } from './index'; +import { TFDataFactory, TFTerm, TFQuad, TFNamedNode, TFSubject, TermType, TFLiteral, TFPredicate, TFObject, TFGraph } from './types'; +import { IndexedFormula, NamedNode, Statement } from './index'; import { docpart } from './uri'; import { string_startswith } from './util'; import log from './log'; @@ -11,6 +11,10 @@ export function isTFStatement(obj: any): obj is TFQuad { return obj && Object.prototype.hasOwnProperty.call(obj, "subject") } +export function isStatement(obj: any): obj is Statement { + return obj && obj instanceof Statement +} + export function isStore(obj: any): obj is IndexedFormula { return obj && Object.prototype.hasOwnProperty.call(obj, "statements") } @@ -36,6 +40,35 @@ export function isCollection(obj: T | TFTerm): obj is Collection { && (obj as TFTerm).termType === TermType.Collection } +export function isTFSubject(obj: any): obj is TFSubject { + return obj && Object.prototype.hasOwnProperty.call(obj, "termType") && ( + obj.termType === TermType.NamedNode || + obj.termType === TermType.BlankNode + ) +} + +export function isTFPredicate(obj: any): obj is TFPredicate { + return obj && Object.prototype.hasOwnProperty.call(obj, "termType") && ( + obj.termType === TermType.NamedNode + ) +} + +export function isTFObject(obj: any): obj is TFObject { + return obj && Object.prototype.hasOwnProperty.call(obj, "termType") && ( + obj.termType === TermType.NamedNode || + obj.termType === TermType.BlankNode || + obj.termType === TermType.Literal + ) +} + +export function isTFGraph(obj: any): obj is TFGraph { + return obj && Object.prototype.hasOwnProperty.call(obj, "termType") && ( + obj.termType === TermType.NamedNode || + obj.termType === TermType.BlankNode || + obj.termType === TermType.DefaultGraph + ) +} + /** Converts NamedNodes to URI strings */ export function uriCreator(input: TFNamedNode | string): string { if (isTFNamedNode(input)) { diff --git a/src/wip.md b/src/wip.md index 65b75eeef..f694f8b5a 100644 --- a/src/wip.md +++ b/src/wip.md @@ -36,6 +36,7 @@ New in this PR: - Removed unused second argument from `Fetcher.cleanupFetchRequest` - Created one huge `Options` type for Fetcher. Not sure if this is the way to go. - In `Node.toJS`, the boolean only returned true if the `xsd:boolean` value is `'1'`, now it it should also work for `'true'`. +- Added typechecks for valid statements, e.g. `isTFPredicate`. Things I noticed: @@ -50,8 +51,11 @@ Things I noticed: - The Variable type (or `TFVariable`) really messes with some assumptions. I feel like they should not be valid in regular quads, since it's impossible to serialize them decently. - Many promise functions could be converted to async. - The `Fetcher` `StatusValues` should be `numbers`, but can be many types in RDFlib. This breaks compatibility with extending Response types. I feel we should only use the `499` browser error and add the message to the `requestbody` -- `store.add()` accepts many types of inputs, but this will lead to invalid statements (e.g. a Literal as a Subject). I suggest we make this more strict and throw more errors on wrong inputs. Relates to #362. We could still make the allowed inputs bigger by allowing other types with some explicit behavior, e.g. in subject arguments, create `NamedNodes` from `URL` objects and `strings` that look like URLs . In any case, I thinkg the `Node.formValue` behavior is too unpredictable for `store.add`. +- `store.add()` accepts many types of inputs, but this will lead to invalid statements (e.g. a Literal as a Subject). I suggest we make this more strict and throw more errors on wrong inputs. Relates to #362. We could still make the allowed inputs bigger by allowing other types with some explicit behavior, e.g. in subject arguments, create `NamedNodes` from `URL` objects and `strings` that look like URLs . In any case, I thinkg the `Node.formValue` behavior is too unpredictable for `store.add`. For now, I've updated the docs to match its behavior. - I'm a bit embarassed about this, but even after rewriting so much of the code I still don't understand all methods. E.g. `Forumla.transitiveClosure()` - The `IndexedFormula` and `Formula` methods have incompatible types, such as in `compareTerm`, `variable` and `add`. This - The concept `graph` is referred to as `why`, `doc` and `graph` in the code and API. I think this might be confusing - we should just call it graph. -- THe `Parse.executeErrorCallback` conditional logic is always `true`. +- The `Parse.executeErrorCallback` conditional logic is always `true`. +- The `Formula.serialize` function calls `serialize.ts` with only one argument, so without a store. I think this will crash every time, maybe it's rotten code? +- `Formula.substitute()` uses `this.add(Statments[])`, which will crash. I think it should be removed, since `IndexedFormula.substitute` is used all the time anyway. +- `IndexedFormula.predicateCallback` is checked, but never used in this codebase.