diff --git a/src/data-factory-internal.ts b/src/data-factory-internal.ts index 1e0dedb2d..fc23feea8 100644 --- a/src/data-factory-internal.ts +++ b/src/data-factory-internal.ts @@ -3,7 +3,15 @@ import Literal from './literal' import NamedNode from './named-node' import Statement from './statement' import Variable from './variable' -import { TFSubject, TFPredicate, TFObject, TFGraph, TFNamedNode } from './types' +import { + TFNamedNode, + SubjectType, + PredicateType, + ObjectType, + GraphType, +} from './types' +import { Feature, IdentityFactory } from './data-factory-type' +import { Node } from './index' export const defaultGraphURI = 'chrome:theSession' @@ -11,14 +19,14 @@ export const defaultGraphURI = 'chrome:theSession' * Creates a new blank node * @param value The blank node's identifier */ -function blankNode(value: string): BlankNode { +function blankNode(value?: string): BlankNode { return new BlankNode(value) } /** * Gets the default graph */ -function defaultGraph(): TFNamedNode { +function defaultGraph(): NamedNode { return new NamedNode(defaultGraphURI) } @@ -27,12 +35,12 @@ function defaultGraph(): TFNamedNode { * * Equivalent to {Term.hashString} */ -function id (term) { +function id (term: Node) { if (!term) { return term } - if (Object.prototype.hasOwnProperty.call(term, "id") && typeof term.id === "function") { - return term.id() + if (Object.prototype.hasOwnProperty.call(term, "id") && typeof (term as NamedNode).id === "function") { + return (term as NamedNode).id() } if (Object.prototype.hasOwnProperty.call(term, "hashString")) { return term.hashString() @@ -93,10 +101,10 @@ function namedNode(value: string): NamedNode { * @param graph The containing graph */ function quad( - subject: TFSubject, - predicate: TFPredicate, - object: TFObject, - graph?: TFGraph + subject: SubjectType, + predicate: PredicateType, + object: ObjectType, + graph?: GraphType ): Statement { graph = graph || defaultGraph() return new Statement(subject, predicate, object, graph) @@ -110,8 +118,7 @@ function variable(name?: string): Variable { return new Variable(name) } -/** Contains the factory methods as defined in the spec, plus id */ -export default { +const CanonicalDataFactory: IdentityFactory = { blankNode, defaultGraph, literal, @@ -120,10 +127,14 @@ export default { variable, id, supports: { - COLLECTIONS: false, - DEFAULT_GRAPH_TYPE: true, - EQUALS_METHOD: true, - NODE_LOOKUP: false, - VARIABLE_TYPE: true, + [Feature.collections]: false, + [Feature.defaultGraphType]: true, + [Feature.equalsMethod]: true, + [Feature.identity]: true, + [Feature.reversibleIdentity]: false, + [Feature.variableType]: true, } } + +/** Contains the factory methods as defined in the spec, plus id */ +export default CanonicalDataFactory diff --git a/src/data-factory-type.ts b/src/data-factory-type.ts new file mode 100644 index 000000000..e18766c18 --- /dev/null +++ b/src/data-factory-type.ts @@ -0,0 +1,94 @@ +import { TFNamedNode, TFBlankNode, TFLiteral, TFQuad, TFTerm, TFDataFactory, TFSubject, TFObject } from "./types" + +/** + * Defines a strict subset of the DataFactory as defined in the RDF/JS: Data model specification + * Non RDF-native features have been removed (e.g. no Variable, no Literal as predicate, etc.). + * bnIndex is optional but useful. + */ +export interface DataFactory< + NamedNode extends TFNamedNode = TFNamedNode, + BlankNode extends TFBlankNode = TFBlankNode, + Literal extends TFLiteral = TFLiteral, + FactoryTypes = NamedNode | TFBlankNode | Literal | TFQuad +> extends TFDataFactory { + bnIndex?: number + + supports: SupportTable + + literal(value: string, languageOrDatatype?: string | NamedNode): TFLiteral + + 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 + + equals(a: Comparable, b: Comparable): boolean + + toNQ(term: FactoryTypes): string +} + +export interface IdentityFactory< + IndexType = Indexable, + FactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | TFQuad +> extends DataFactory { + /** + * Generates a unique session-idempotent identifier for the given object. + * + * @example NQ serialization (reversible from value) + * @example MD5 hash of termType + value (irreversible from value, map needed) + * + * @return {Indexable} A unique value which must also be a valid JS object key type. + */ + id(obj: FactoryTypes): IndexType | unknown + +} + +/** + * Factory type which supports reverse id lookups. + * + * It should be able to resolve the value for any given id which it handed out. Passing an id not + * generated by the same instance might result in a value or an exception depending on the + * implementation. + */ +export interface ReversibleIdentityFactory< + IndexType = Indexable, + FactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | TFQuad +> extends IdentityFactory { + fromId(id: IndexType): FactoryTypes; +} + +export type Namespace = (term:string) => TFNamedNode +export type NamespaceCreator = (ns: string) => Namespace + +export type SupportTable = Record + +export enum Feature { + /** Whether the factory supports termType:Collection terms */ + collections = "COLLECTIONS", + /** Whether the factory supports termType:DefaultGraph terms */ + defaultGraphType = "DEFAULT_GRAPH_TYPE", + /** Whether the factory supports equals on produced instances */ + equalsMethod = "EQUALS_METHOD", + /** Whether the factory can generate a unique session-idempotent identifier for a given object */ + identity = "IDENTITY", + /** Whether the factory supports mapping ids back to instances */ + reversibleIdentity = "REVERSIBLE_IDENTITY", + /** Whether the factory supports termType:Variable terms */ + variableType = "VARIABLE_TYPE", +} + +export type Comparable = TFNamedNode | TFBlankNode | TFLiteral | TFQuad | undefined | null + +export type Indexable = number | string diff --git a/src/data-factory.ts b/src/data-factory.ts index dc857a3d1..7f4963d9f 100644 --- a/src/data-factory.ts +++ b/src/data-factory.ts @@ -3,9 +3,11 @@ import CanonicalDataFactory from './data-factory-internal' import Fetcher from './fetcher' import Literal from './literal' import Statement from './statement' -import { ValueType, TFNamedNode, TFSubject, TFPredicate, TFObject, TFGraph } from './types' +import { ValueType, TFNamedNode, TFSubject, TFPredicate, TFObject, TFGraph, SubjectType, PredicateType, ObjectType, GraphType } from './types' import IndexedFormula from './store' import Formula from './formula' +import { DataFactory } from './data-factory-type' +import { NamedNode } from './index' /** * Data factory which also supports Collections @@ -25,8 +27,25 @@ const ExtendedTermFactory = { } } +interface IRDFlibDataFactory extends DataFactory { + fetcher: (store: Formula, options: any) => Fetcher + graph: (features, opts) => IndexedFormula + lit: (val: string, lang?: string, dt?: TFNamedNode) => Literal + st: ( + subject: TFSubject, + predicate: TFPredicate, + object: TFObject, + graph?: TFGraph + ) => Statement + triple: ( + subject: TFSubject, + predicate: TFPredicate, + object: TFObject + ) => Statement +} + /** Full RDFLib.js Data Factory */ -const DataFactory = { +const RDFlibDataFactory: IRDFlibDataFactory = { ...ExtendedTermFactory, fetcher, graph, @@ -34,7 +53,8 @@ const DataFactory = { st, triple, } -export default DataFactory + +export default RDFlibDataFactory function id (term) { if (!term) { @@ -91,12 +111,11 @@ function lit(val: string, lang?: string, dt?: TFNamedNode): Literal { * @param graph The containing graph */ function st( - subject: TFSubject, - predicate: TFPredicate, - object: TFObject, - graph?: TFGraph -): Statement; -function st (subject, predicate, object, graph) { + subject: SubjectType, + predicate: PredicateType, + object: ObjectType, + graph?: GraphType +): Statement { return new Statement(subject, predicate, object, graph) } /** diff --git a/src/default-graph.ts b/src/default-graph.ts index b87a107d0..4ed66d360 100644 --- a/src/default-graph.ts +++ b/src/default-graph.ts @@ -5,7 +5,7 @@ import { TFDefaultGraph, TermType, DefaultGraphTermType } from './types'; * The RDF default graph */ export default class DefaultGraph extends Node implements TFDefaultGraph { - value: '' + value: string termType: DefaultGraphTermType; constructor () { diff --git a/src/formula.ts b/src/formula.ts index 598706d2e..a61d1a9db 100644 --- a/src/formula.ts +++ b/src/formula.ts @@ -25,13 +25,15 @@ import { } from './types' import Variable from './variable' import Literal from './literal' +import { IdentityFactory, Indexable } from './data-factory-type' +import IndexedFormula from './store' export function isFormula(value: T | TFTerm): value is Formula { return (value as Node).termType === TermType.Graph } interface FormulaOpts { - rdfFactory?: TFDataFactory + rdfFactory?: IdentityFactory } /** @@ -61,7 +63,7 @@ export default class Formula extends Node { optional: ReadonlyArray /** The factory used to generate statements and terms */ - rdfFactory: TFDataFactory + rdfFactory: IdentityFactory /** * Initializes this formula @@ -113,7 +115,7 @@ export default class Formula extends Node { predicate: TFPredicate, object: TFObject, graph?: TFGraph - ) { + ): number { return (this.statements as Statement[]) .push(this.rdfFactory.quad(subject, predicate, object, graph)) } @@ -234,7 +236,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) { + id (term: TFTerm): Indexable { return this.rdfFactory.id(term) } @@ -718,7 +720,7 @@ fromNT(str: string): TFTerm { * @param context - The store * @return The term for the statement */ - list(values: Iterable, context): Collection | BlankNode { + list(values: [], context: IndexedFormula): Collection | TFBlankNode { if (context.rdfFactory.supports["COLLECTIONS"]) { const collection = context.rdfFactory.collection() values.forEach(function (val) { @@ -802,7 +804,7 @@ fromNT(str: string): TFTerm { /** * Gets a new formula with the substituting bindings applied - * @param bindings The bindings to substitute + * @param bindings - The bindings to substitute */ substitute(bindings: Bindings): Formula { var statementsCopy = this.statements.map(function (ea) { @@ -940,7 +942,7 @@ fromNT(str: string): TFTerm { } /** - * Serializes this formulat to a string + * Serializes this formula to a string */ toString(): string { return '{' + this.statements.join('\n') + '}' diff --git a/src/literal.ts b/src/literal.ts index f4079b269..ed7dae711 100644 --- a/src/literal.ts +++ b/src/literal.ts @@ -1,9 +1,9 @@ -import NamedNode, { isNamedNode } from './named-node' +import NamedNode from './named-node' import Node from './node-internal' import XSD from './xsd-internal' import { ValueType, TFTerm, LiteralTermType, TFLiteral, TFNamedNode, TermType } from './types' import classOrder from './class-order' -import { isTFTerm } from './utils' +import { isTFTerm, isNamedNode } from './utils' export function isTFLiteral(value: T | TFTerm): value is TFLiteral { return (value as TFTerm).termType === TermType.Literal @@ -88,7 +88,8 @@ export default class Literal extends Node implements TFLiteral { toNT() { return Literal.toNT(this) } - static toNT (literal) { + /** Serializes a literal to an N-Triples string */ + static toNT (literal: Literal): string { if (typeof literal.value === 'number') { return '' + literal.value } else if (typeof literal.value !== 'string') { @@ -161,7 +162,7 @@ export default class Literal extends Node implements TFLiteral { * Builds a literal node from an input value * @param value The input value */ - static fromValue(value: ValueType): TFTerm { + static fromValue(value: ValueType): Literal | TFTerm { if (isTFTerm(value)) { return value } diff --git a/src/named-node.ts b/src/named-node.ts index 96d2ec24d..3cbf26c36 100644 --- a/src/named-node.ts +++ b/src/named-node.ts @@ -15,7 +15,7 @@ export default class NamedNode extends Node implements TFNamedNode { termType: NamedNodeTermType; /** - * Initializes this node + * Create a named (IRI) RDF Node * @param iri The IRI for this node */ constructor (iri: NamedNode | string) { diff --git a/src/node-internal.ts b/src/node-internal.ts index f0e6b02a3..529078e35 100644 --- a/src/node-internal.ts +++ b/src/node-internal.ts @@ -46,9 +46,9 @@ export default abstract class Node { * Gets the substituted node for this one, according to the specified bindings * @param bindings Bindings of identifiers to nodes */ - substitute (bindings: Bindings): TFTerm { + substitute (bindings: Bindings): T { console.log('@@@ node substitute' + this) - return this + return this as unknown as T } /** diff --git a/src/node.ts b/src/node.ts index 2886c4311..4b259c997 100644 --- a/src/node.ts +++ b/src/node.ts @@ -5,8 +5,8 @@ import Collection from './collection' import Literal from './literal' import { ValueType, TFTerm } from './types' import Namespace from './namespace' -import { isCollection } from './collection'; import { isTFLiteral } from './literal'; +import { isCollection } from './utils' export default Node diff --git a/src/serialize.ts b/src/serialize.ts index 1de59ddad..f283cf7f9 100644 --- a/src/serialize.ts +++ b/src/serialize.ts @@ -1,52 +1,66 @@ import * as convert from './convert' import Serializer from './serializer' +import { ContentType } from './types' +import IndexedFormula from './store' /** * Serialize to the appropriate format */ -export default function serialize (target, kb, base, contentType, callback, options) { +export default function serialize ( + target, + /** The store */ + kb: IndexedFormula, + base?, + /** + * The mime type. + * Defaults to Turtle. + */ + contentType?: ContentType, + callback?: (err: Error, result?: any ) => void, + options? +) { base = base || target.uri options = options || {} - contentType = contentType || 'text/turtle' // text/n3 if complex? + 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 n3String + var n3String: string sz.suggestNamespaces(kb.namespaces) sz.setBase(base) switch (contentType) { - case 'application/rdf+xml': + case ContentType.rdfxml: documentString = sz.statementsToXML(newSts) return executeCallback(null, documentString) - case 'text/n3': - case 'application/n3': // Legacy + case ContentType.n3: + case ContentType.n3Legacy: documentString = sz.statementsToN3(newSts) return executeCallback(null, documentString) - case 'text/turtle': - case 'application/x-turtle': // Legacy + case ContentType.turtle: + case ContentType.turtleLegacy: sz.setFlags('si') // Suppress = for sameAs and => for implies documentString = sz.statementsToN3(newSts) return executeCallback(null, documentString) - case 'application/n-triples': + case ContentType.nTriples: sz.setFlags('deinprstux') // Suppress nice parts of N3 to make ntriples documentString = sz.statementsToNTriples(newSts) return executeCallback(null, documentString) - case 'application/ld+json': + case ContentType.jsonld: sz.setFlags('deinprstux') // Use adapters to connect to incmpatible parser n3String = sz.statementsToNTriples(newSts) // n3String = sz.statementsToN3(newSts) convert.convertToJson(n3String, callback) break - case 'application/n-quads': - case 'application/nquads': // @@@ just outpout the quads? Does not work for collections + case ContentType.nQuads: + case ContentType.nQuadsAlt: // @@@ just outpout the quads? Does not work for collections sz.setFlags('deinprstux q') // Suppress nice parts of N3 to make ntriples documentString = sz.statementsToNTriples(newSts) // q in flag means actually quads return executeCallback(null, documentString) // n3String = sz.statementsToN3(newSts) // documentString = convert.convertToNQuads(n3String, callback) - break + // break default: throw new Error('Serialize: Content-type ' + contentType + ' not supported for data write.') } diff --git a/src/statement.ts b/src/statement.ts index 8215b15e6..725e99f4e 100644 --- a/src/statement.ts +++ b/src/statement.ts @@ -1,27 +1,32 @@ -import { Bindings, SubjectType, PredicateType, ObjectType, GraphType, TFQuad, TFNamedNode, SomeNode, TermType } from './types' +import { + Bindings, + GraphType, + ObjectType, + PredicateType, + SubjectType, + TermType, + TFQuad, +} from './types' import Literal from './literal' import Node from './node-internal' -import { NamedNode, Collection, defaultGraph } from './index'; -import BlankNode from './blank-node'; - -type StObjectType = NamedNode | Literal | Collection | BlankNode +import { NamedNode, defaultGraph } from './index'; /** A Statement represents an RDF Triple or Quad. */ -export default class Statement implements TFQuad { +export default class Statement implements TFQuad { /** The subject of the triple. What the Statement is about. */ - subject: SomeNode + subject: SubjectType /** The relationship which is asserted between the subject and object */ - predicate: NamedNode + predicate: PredicateType /** The thing or data value which is asserted to be related to the subject */ - object: StObjectType + object: ObjectType /** * The why param is a named node of the document in which the triple when * it is stored on the web. */ - why: TFNamedNode + graph: GraphType /** * Construct a new Triple Statment @@ -43,26 +48,26 @@ export default class Statement implements TFQuad(subject) + this.subject = Node.fromValue(subject) this.predicate = Node.fromValue(predicate) this.object = Node.fromValue(object) - this.why = why as NamedNode // property currently used by rdflib - if (why == undefined) { - this.why = defaultGraph() + this.graph = graph as NamedNode // property currently used by rdflib + if (graph == undefined) { + this.graph = defaultGraph() as GraphType } } /** * The graph param is a named node of the document in which the triple is stored on the web. */ - get graph () { - return this.why + get why () { + return this.graph } - set graph (g) { - this.why = g + set why (g) { + this.graph = g } /** @@ -86,8 +91,9 @@ export default class Statement implements TFQuad(bindings) + ) // 2016 console.log('@@@ statement substitute:' + y) return y } diff --git a/src/store.ts b/src/store.ts index f86aa4af7..402a374c1 100644 --- a/src/store.ts +++ b/src/store.ts @@ -26,9 +26,10 @@ import Variable from './variable' import { Query, indexedFormulaQuery } from './query' import { RDFArrayRemove } from './util'; import UpdateManager from './update-manager'; -import { TFDataFactory, Bindings, TFTerm, TFPredicate, TFSubject, TFObject, TFGraph } from './types' +import { TFDataFactory, Bindings, TFTerm, TFPredicate, TFSubject, TFObject, TFGraph, TFQuad, SubjectType, PredicateType, ObjectType, GraphType } from './types' import { NamedNode } from './index' import Statement from './statement'; +import { Indexable } from './data-factory-type' const owlNamespaceURI = 'http://www.w3.org/2002/07/owl#' @@ -99,16 +100,21 @@ export default class IndexedFormula extends Formula { // IN future - allow pass /** Reverse mapping to redirection: aliases for this */ aliases: any[] /** Redirections we got from HTTP */ - HTTPRedirects: any[] + HTTPRedirects: TFQuad[] /** Array of statements with this X as subject */ - subjectIndex: any[] + subjectIndex: TFQuad[] /** Array of statements with this X as predicate */ - predicateIndex: any[] + predicateIndex: TFQuad[] /** Array of statements with this X as object */ - objectIndex: any[] + objectIndex: TFQuad[] /** Array of statements with X as provenance */ - whyIndex: any[] - index: any[] + whyIndex: TFQuad[] + index: [ + TFQuad[], + TFQuad[], + TFQuad[], + TFQuad[] + ] features: FeaturesType /** @@ -118,7 +124,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?: { rdfFactory: TFDataFactory}) { super(undefined, undefined, undefined, undefined, opts) this.propertyActions = [] @@ -316,7 +322,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass subj: TFSubject, pred: TFPredicate, obj: TFObject, - why: TFGraph + why?: TFGraph ): Statement { var i if (arguments.length === 1) { @@ -573,10 +579,10 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @param graph The graph that contains the statement */ match( - subject: TFTerm, - predicate: TFTerm, - object: TFTerm, - graph: TFTerm + subject?: TFSubject | null, + predicate?: TFPredicate | null, + object?: TFObject | null, + graph?: TFGraph | null ): Statement[] { return this.statementsMatching( Node.fromValue(subject), @@ -744,10 +750,10 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @param limit The number of statements to remove */ removeMany( - subj?: Node | null, - pred?: Node | null, - obj?: Node | null, - why?: Node | null, + subj?: TFSubject | null, + pred?: TFPredicate | null, + obj?: TFObject | null, + why?: TFGraph | null, limit?: number ): void { // log.debug("entering removeMany w/ subj,pred,obj,why,limit = " + subj +", "+ pred+", " + obj+", " + why+", " + limit) @@ -770,13 +776,14 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @param graph The graph that contains the statement */ removeMatches( - subject?: Node | null, - predicate?: Node | null, - object?: Node | null, - graph?: Node | null + subject?: SubjectType | null, + predicate?: PredicateType | null, + object?: ObjectType | null, + graph?: GraphType | null ): IndexedFormula { - this.removeStatements(this.statementsMatching(subject, predicate, object, - why)) + this.removeStatements( + this.statementsMatching(subject, predicate, object,why) + ) return this } @@ -907,27 +914,28 @@ export default class IndexedFormula extends Formula { // IN future - allow pass /** Search the Store * * ALL CONVENIENCE LOOKUP FUNCTIONS RELY ON THIS! - * @param {Node} subject - A node to search for as subject, or if null, a wildcard - * @param {Node} predicate - A node to search for as predicate, or if null, a wildcard - * @param {Node} object - A node to search for as object, or if null, a wildcard - * @param {Node} graph - A node to search for as graph, or if null, a wildcard - * @param {Boolean} justOne - flag - stop when found one rather than get all of them? - * @returns {Array} - An array of nodes which match the wildcard position + * @param subject - A node to search for as subject, or if null, a wildcard + * @param predicate - A node to search for as predicate, or if null, a wildcard + * @param object - A node to search for as object, or if null, a wildcard + * @param graph - A node to search for as graph, or if null, a wildcard + * @param justOne - flag - stop when found one rather than get all of them? + * @returns An array of nodes which match the wildcard position */ statementsMatching ( - subj: TFTerm, - pred: TFTerm, - obj: TFTerm, - why: TFTerm + subj?: TFSubject | null, + pred?: TFPredicate | null, + obj?: TFObject | null, + why?: TFGraph | null, + justOne?: boolean ): Statement[] { // log.debug("Matching {"+subj+" "+pred+" "+obj+"}") var pat = [ subj, pred, obj, why ] - var pattern = [] - var hash = [] - var wild = [] // wildcards - var given = [] // Not wild - var p + var pattern: TFTerm[] = [] + var hash: Indexable[] = [] + var wild: number[] = [] // wildcards + var given: number[] = [] // Not wild + var p: number var list for (p = 0; p < 4; p++) { pattern[p] = this.canon(Node.fromValue(pat[p])) diff --git a/src/types.ts b/src/types.ts index b8b161e7b..5bbfca9d3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -28,17 +28,42 @@ export enum TermType { Graph = "Graph", } +/** 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", +} + +/** A type for values that serves as inputs */ +export type ValueType = TFTerm | Node | Date | string | number | boolean | undefined | null | Collection; + /** * In this project, there exist two types for the same kind of RDF concept. - * We have RDFJS Taskforce types (standardized, generic), and RDFlib types (internal, specific). + * We have RDF/JS Taskforce types (standardized, generic), and RDFlib types (internal, specific). * When deciding which type to use in a function, it is preferable to accept generic inputs, * whenever possible, and provide strict outputs. * In some ways, the TF types in here are a bit more strict. * Variables are missing, and the statement requires specific types of terms (e.g. NamedNode instead of Term). */ +/** An RDF/JS Subject */ +export type SubjectType = BlankNode | NamedNode | Variable +/** An RDF/JS Predicate */ +export type PredicateType = NamedNode | Variable +/** An RDF/JS Object */ +export type ObjectType = NamedNode | Literal | Collection | BlankNode | Variable +/** An RDF/JS Graph */ +export type GraphType = DefaultGraph | NamedNode | Variable + /** - * RDF.js taskforce Term + * RDF/JS taskforce Term * @link https://rdf.js.org/data-model-spec/#term-interface */ export interface TFTerm { @@ -48,7 +73,7 @@ export interface TFTerm { } /** - * RDF.js taskforce NamedNode + * RDF/JS taskforce NamedNode * @link https://rdf.js.org/data-model-spec/#namednode-interface */ export interface TFNamedNode extends TFTerm { @@ -58,7 +83,7 @@ export interface TFNamedNode extends TFTerm { } /** - * RDF.js taskforce Literal + * RDF/JS taskforce Literal * @link https://rdf.js.org/data-model-spec/#literal-interface */ export interface TFBlankNode extends TFTerm { @@ -68,13 +93,13 @@ export interface TFBlankNode extends TFTerm { }; /** - * RDF.js taskforce Quad + * RDF/JS taskforce Quad * @link https://rdf.js.org/data-model-spec/#quad-interface */ export interface TFQuad< S extends TFSubject = TFSubject, P extends TFPredicate = TFPredicate, - O extends TFObject = TFObject, + O extends TFTerm = TFObject, G extends TFGraph = TFGraph > { subject: S @@ -85,7 +110,7 @@ export interface TFQuad< } /** - * RDF.js taskforce Literal + * RDF/JS taskforce Literal * @link https://rdf.js.org/data-model-spec/#literal-interface */ export interface TFLiteral extends TFTerm { @@ -104,7 +129,7 @@ export interface TFLiteral extends TFTerm { }; /** - * RDF.js taskforce Variable + * RDF/JS taskforce Variable * @link https://rdf.js.org/data-model-spec/#variable-interface */ export interface TFVariable extends TFTerm { @@ -120,39 +145,47 @@ export interface TFVariable extends TFTerm { }; /** - * RDF.js taskforce DefaultGraph + * RDF/JS taskforce DefaultGraph * An instance of DefaultGraph represents the default graph. * It's only allowed to assign a DefaultGraph to the graph property of a Quad. * @link https://rdf.js.org/data-model-spec/#defaultgraph-interface */ export interface TFDefaultGraph extends TFTerm { termType: DefaultGraphTermType; - value: ''; + /** should return and empty string'' */ + value: string; equals(other: TFTerm): boolean }; /** - * RDF.js taskforce DataFactory + * RDF/JS taskforce DataFactory * @link https://rdf.js.org/data-model-spec/#datafactory-interface */ -export interface TFDataFactory { +export interface TFDataFactory< + DFNamedNode extends TFNamedNode = TFNamedNode, + DFBlankNode extends TFBlankNode = TFBlankNode, + DFLiteral extends TFLiteral = TFLiteral, +> { /** Returns a new instance of NamedNode. */ - namedNode: (value: string) => TFNamedNode, + namedNode: (value: string) => DFNamedNode, /** * Returns a new instance of BlankNode. * If the value parameter is undefined a new identifier for the * blank node is generated for each call. */ - blankNode: (value?: string) => TFBlankNode, + blankNode: (value?: string) => DFBlankNode, /** * Returns a new instance of Literal. * If languageOrDatatype is a NamedNode, then it is used for the value of datatype. * Otherwise languageOrDatatype is used for the value of language. */ - literal: (value: string, languageOrDatatype: string | TFNamedNode) => TFLiteral, + literal: (value: string, languageOrDatatype: string | DFNamedNode) => DFLiteral, /** Returns a new instance of Variable. This method is optional. */ variable?: (value: string) => TFVariable, - /** Returns an instance of DefaultGraph. */ - defaultGraph: () => TFDefaultGraph, + /** + * Returns an instance of DefaultGraph. + * Note: This differs from the TF spec! + */ + defaultGraph: () => TFDefaultGraph | DFNamedNode | DFBlankNode, /** * Returns a new instance of the specific Term subclass given by original.termType * (e.g., NamedNode, BlankNode, Literal, etc.), @@ -163,30 +196,27 @@ export interface TFDataFactory { * Returns a new instance of Quad, such that newObject.equals(original) returns true. */ fromQuad: (original: TFQuad) => TFQuad + /** + * 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 } -/** -* A type for values that serves as inputs -*/ -export type ValueType = TFTerm | Node | Date | string | number | boolean | undefined | null | Collection; - export interface Bindings { [id: string]: TFTerm; } -export type TFSomeNode = TFBlankNode | TFNamedNode -export type SomeNode = NamedNode | BlankNode - -/** A set of allowable input types for statements and related methods. */ - -export type TFSubject = TFNamedNode | TFBlankNode -export type TFPredicate = TFNamedNode -export type TFObject = TFNamedNode | TFBlankNode | TFLiteral -export type TFGraph = TFNamedNode | TFDefaultGraph - -/** RDFJS types */ - -export type SubjectType = NamedNode | Literal | Variable -export type PredicateType = NamedNode | Variable -export type ObjectType = NamedNode | Literal | Collection | BlankNode | Variable -export type GraphType = DefaultGraph | NamedNode | Variable +/** A RDF/JS taskforce Subject */ +export type TFSubject = TFNamedNode | TFBlankNode | TFVariable +/** A RDF/JS taskforce Predicate */ +export type TFPredicate = TFNamedNode | TFVariable +/** A RDF/JS taskforce Object */ +export type TFObject = TFNamedNode | TFBlankNode | TFLiteral | TFVariable +/** A RDF/JS taskforce Graph */ +export type TFGraph = TFNamedNode | TFDefaultGraph | TFBlankNode | TFVariable diff --git a/src/update-manager.ts b/src/update-manager.ts index 6e9fe63cb..17621ec39 100644 --- a/src/update-manager.ts +++ b/src/update-manager.ts @@ -13,8 +13,8 @@ import Serializer from './serializer' import { join as uriJoin } from './uri' import { isStore } from './utils' import * as Util from './util' -import Node from './node'; import Statement from './statement'; +import { NamedNode } from './index' /** Update Manager * @@ -304,9 +304,9 @@ export default class UpdateManager { * @private */ mentioned (x) { - return this.store.statementsMatching(x).length !== 0 || // Don't pin fresh bnodes - this.store.statementsMatching(undefined, x).length !== 0 || - this.store.statementsMatching(undefined, undefined, x).length !== 0 + return this.store.statementsMatching(x, null, null, null).length !== 0 || // Don't pin fresh bnodes + this.store.statementsMatching(null, x).length !== 0 || + this.store.statementsMatching(null, null, x).length !== 0 } /** @@ -1018,7 +1018,7 @@ export default class UpdateManager { * @param callback */ put( - document: Node, + doc: NamedNode, data: string | ReadonlyArray, contentType: string, callback: (uri: string, ok: boolean, errorMessage: string, response?: unknown) => void, @@ -1035,7 +1035,7 @@ export default class UpdateManager { }) .then(response => { if (!response.ok) { - return callbackFunction(doc.uri, response.ok, response.error, response) + return callback(doc.uri, response.ok, response.error, response) } delete kb.fetcher.nonexistent[doc.uri] @@ -1047,10 +1047,10 @@ export default class UpdateManager { }) } - callbackFunction(doc.uri, response.ok, '', response) + callback(doc.uri, response.ok, '', response) }) .catch(err => { - callbackFunction(doc.uri, false, err.message) + callback(doc.uri, false, err.message) }) } diff --git a/src/wip.md b/src/wip.md index 18f4a1a41..8ad758c98 100644 --- a/src/wip.md +++ b/src/wip.md @@ -1,29 +1,32 @@ Builds upon the approved #363 PR for [typescript migration](https://github.com/linkeddata/rdflib.js/issues/355): -- Converted some of the most fundamental classes to typescript, including Node, Literal, BlankNode, NamedNode, Collection, Statement. -- Introduced a .types file for shared types. -- Included a temporary types-temp.ts file in project root as a reference file for documentation and keeping track of the ts migration process. -- The .isVar method is set to boolean values, instead of 0 or 1. This seemed reasonable, as it's only used for boolean type checks, and the existing types already define it as a boolean value. +- Converted some of the most fundamental classes to typescript, including `Node`, `Literal`, `BlankNode`, `NamedNode`, `Collection`, `Statement`. +- Introduced a `.types` file for shared types. +- Included a temporary `types-temp.ts` file in project root as a reference file for documentation and keeping track of the ts migration process. +- The `.isVar` method is set to boolean values, instead of `0` or `1`. This seemed reasonable, as it's only used for boolean type checks, and the existing types already define it as a boolean value. Timbl confirmed that `isVar` is only used for boolean operations. - JSDoc is switched with Typedoc. Combined with typescript, it makes the documentation far more complete. - Sometimes I had to make assumptions on date types, e.g. that a user will never create a Statement containing a Collection. These assumptions used to be implicit, but typescript forces us to make them explicit. This happens when you see type assertions (value as type). -- Literals can apparently be null or undefined, when nodes are created using the .fromValue method. This happens in the Statement constructor. See #362. +- Literals can apparently be null or undefined, when nodes are created using the `.fromValue` method. This happens in the Statement constructor. See #362. +- I used many of the descriptions and comments from `@types/rdflib` by [Cénotélie](https://github.com/cenotelie/). Added credits in `package.json`, discussed this with Cénotélie. New in this PR: -- Added and implemented RDFJS Taskforce (TF) types, included these in the `types.ts` file. I tried implementing the TF types in the major classes, but some of the incompatibilities make it difficult. Many available methods on rdfjs instances (e.g. `.toNt()` in NamedNode), are missing in TF classes. To improve TF comatibility, we should minimize using rdflib specific functions. This would for example enable using Forumla methods on RDFExt nodes. We should use the Taskforce types (Term, RDFJSQuad) as much as possible, instead of rdflib types (Node, Statement). +- Added and implemented RDF/JS Taskforce (TF) types, included these in the `types.ts` file. I tried implementing the TF types in the major classes, but some of the incompatibilities make it difficult. Many available methods on rdfjs instances (e.g. `.toNt()` in NamedNode), are missing in TF classes. To improve TF comatibility, we should minimize using rdflib specific functions. This would for example enable using Forumla methods on RDFExt nodes. We should use the Taskforce types (TFTerm, TFQuad) as much as possible, instead of rdflib types (Node, Statement). - Variables (from rdfjs taskforce) make typings a lot more complex, so I left them out. - Switched internal calls from `sameTerm` to `equals` in order to comply to TF spec. -- Migrated `formula`, `variable`, `store`, `update-manager`, `data-factory` and `parse` tot ts -- Added typeguards, e.g. `isTFNamedNode` +- Migrated `formula`, `variable`, `store`, `update-manager`, `data-factory`, `default-graph`, `namespace`, `parse`,`serialize`, `parse`, `uri` and `utils` tot ts. +- Added `fork-ts-checker-webpack-plugin`, which enables errors in the log when ts errors occur. +- Added typeguards, e.g. `isTFNamedNode` in `Utils` - Use enums for `termType`, without breaking compatibility with regular strings - The 'optional' argument in `formula.js` does not seem to be documented, used or tested - should it be removed? -- Removed the `justOne` argument from `formula.statementsMatching`, since it was unused. Also edited the call in `anyStatementMatching` +- Removed the `justOne` argument from `formula.statementsMatching`, since it was unused. - Removed unreachable code and unused variables. - `Node.substitute()` didn't to anything, so I removed it. - Formula Constructor arguments are optional - since some functions initialize empty Formulas - Removed the last conditional of `Formula.holds()`, since it did not make sense - In `Formula.fromNT()` `return this.literal(str, lang || dt)` seemed wrong, converted it to - The various `fromValue` methods conflict with the base Node class, type wise. Since they don't use `this`, I feel like they should be converted to functions. +- The `uri.document` function called `.uri` on a string, I removed that. Things I noticed: @@ -31,3 +34,6 @@ Things I noticed: - In `Node.toJS`, the boolean only returns true is the `xsd:boolean` value is `'1'`, but I think it should also work for `'true'`. - The `IndexedFormula.add()` method has logic for Statement array inputs and store inputs, but this behavior is not documented. It also refers to `this.fetcher` and `this.defaultGraph`, which both should not be available. - The filenames of major classes differ from +- We have a lot of names for a bunch of Statements. Graph, Document, Store, Forumula, Collection, Resource... Many of these terms have semantic +- Aliases (e.g. `IndexedFormula.match`) introduce extra complexity, documentation and type duplication. I suggest deprecating them in a future version (1.1?) and adding deprecation warnings in the current version. +- the `IndexedFormula` name is different from the `store` filename. I think it might be better to just call it `store` everywhere.