parent
bfce541e8f
commit
9167a16641
@ -0,0 +1,3 @@ |
||||
{ |
||||
"extends": ["../../.eslintrc"] |
||||
} |
@ -0,0 +1,65 @@ |
||||
import { createDataset } from "../src"; |
||||
import { quad, namedNode, literal } from "@rdfjs/data-model"; |
||||
// Required for advanced features:
|
||||
import { dataset as initializeDatasetCore } from "@rdfjs/dataset"; |
||||
import { ExtendedDatasetFactory } from "../src"; |
||||
import type { |
||||
Dataset, |
||||
Quad, |
||||
DatasetCoreFactory, |
||||
DatasetCore, |
||||
} from "@rdfjs/types"; |
||||
|
||||
/** |
||||
* Create a dataset with default settings |
||||
*/ |
||||
const defaultDataset = createDataset(); |
||||
|
||||
/** |
||||
* Create a dataset with default settings and initialized values |
||||
*/ |
||||
const initializedQuads = [ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Tom"), |
||||
), |
||||
]; |
||||
const defaultDataset2 = createDataset(initializedQuads); |
||||
|
||||
/** |
||||
* (Advanced Feature) Create a dataset by injecting a chosen datasetCore and datasetCoreFactory |
||||
*/ |
||||
const datasetFactory: DatasetCoreFactory = { |
||||
dataset: (quads?: Dataset<Quad> | Quad[]): DatasetCore => { |
||||
return initializeDatasetCore( |
||||
Array.isArray(quads) ? quads : quads?.toArray(), |
||||
); |
||||
}, |
||||
}; |
||||
const extendedDatasetFactory = new ExtendedDatasetFactory(datasetFactory); |
||||
const customDataset = extendedDatasetFactory.dataset(initializedQuads); |
||||
|
||||
/** |
||||
* Do all the methods of the RDFJS Dataset interface. For a full list of methods, go to |
||||
* https://rdf.js.org/dataset-spec/#data-interfaces
|
||||
*/ |
||||
defaultDataset.add( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Miuki"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
); |
||||
const combinedDataset = defaultDataset.union(defaultDataset2); |
||||
const differenceDataset = combinedDataset.difference(customDataset); |
||||
// Prints true because "defaultDataset2" and "customDataset" have equal values
|
||||
// combinedDataset = defaultDataset ∪ defaultDataset2
|
||||
// differenceDatasset = defaultDataset \ customDataset
|
||||
// Therefore differenceDataset == defaultDataset
|
||||
console.log(differenceDataset.equals(defaultDataset)); |
@ -0,0 +1,45 @@ |
||||
import { serializedToDataset, serializedToSubscribableDataset } from "../src"; |
||||
|
||||
async function run(): Promise<void> { |
||||
// Create an ExtendedDataset using Turtle
|
||||
const turtleData = ` |
||||
@prefix : <#>. |
||||
@prefix elem: <http://purl.org/dc/elements/1.1/>. |
||||
@prefix card: </profile/card#>. |
||||
|
||||
:this |
||||
elem:author card:me. |
||||
`;
|
||||
const turtleDataset = await serializedToDataset(turtleData, { |
||||
baseIRI: |
||||
"https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#", |
||||
// NOTE: the "format" field isn't required because Turtle is the default parser
|
||||
}); |
||||
|
||||
// Create a SubcribableDataset using JSON-LD
|
||||
const jsonLdData = [ |
||||
{ |
||||
"@id": |
||||
"https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#this", |
||||
"http://purl.org/dc/elements/1.1/author": [ |
||||
{ |
||||
"@id": "https://jackson.solidcommunity.net/profile/card#me", |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
"@id": "https://jackson.solidcommunity.net/profile/card#me", |
||||
}, |
||||
]; |
||||
const jsonLdDataset = await serializedToSubscribableDataset( |
||||
JSON.stringify(jsonLdData), |
||||
{ |
||||
baseIRI: |
||||
"https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#", |
||||
format: "application/json-ld", |
||||
}, |
||||
); |
||||
// Returns true because the input data describes the same triple.
|
||||
console.log(turtleDataset.equals(jsonLdDataset)); |
||||
} |
||||
run(); |
@ -0,0 +1,187 @@ |
||||
import type { DatasetChanges } from "../src"; |
||||
import { createSubscribableDataset } from "../src"; |
||||
import { quad, namedNode, literal } from "@rdfjs/data-model"; |
||||
import type { Dataset } from "@rdfjs/types"; |
||||
|
||||
// Create an empty subscribable dataset
|
||||
const subscribableDataset = createSubscribableDataset(); |
||||
// Add some initial quads
|
||||
subscribableDataset.addAll([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Zuko"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Firebender"), |
||||
namedNode("http://example.org/cartoons"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Zuko"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Zuko"), |
||||
namedNode("http://example.org/cartoons"), |
||||
), |
||||
]); |
||||
// Set up listeners
|
||||
// Listener that will trigger whenever a quad containing the named
|
||||
// node "http://example.org/cartoons#Zuko" is added or removed.
|
||||
subscribableDataset.on( |
||||
namedNode("http://example.org/cartoons#Zuko"), |
||||
(zukoQuads: Dataset, changes: DatasetChanges) => { |
||||
console.log("ZUKO NODE CHANGED ============"); |
||||
console.log(zukoQuads.toString()); |
||||
console.log("Added Quads:"); |
||||
console.log(changes.added?.toString()); |
||||
console.log("Removed Quads:"); |
||||
console.log(changes.removed?.toString()); |
||||
console.log("\n\n"); |
||||
}, |
||||
); |
||||
// Listener that will trigger whenever a quad containing the named
|
||||
// node "http://example.org/cartoons" is added or removed. This is
|
||||
// useful for keeping track of the cartoons graph.
|
||||
subscribableDataset.on( |
||||
namedNode("http://example.org/cartoons"), |
||||
(cartoonGraphQuads: Dataset, changes: DatasetChanges) => { |
||||
console.log("CARTOON GRAPH CHANGED ============"); |
||||
console.log(cartoonGraphQuads.toString()); |
||||
console.log("Added Quads:"); |
||||
console.log(changes.added?.toString()); |
||||
console.log("Removed Quads:"); |
||||
console.log(changes.removed?.toString()); |
||||
console.log("\n\n"); |
||||
}, |
||||
); |
||||
|
||||
// Modify the dataset
|
||||
/* |
||||
Prints: |
||||
CARTOON GRAPH CHANGED ============ |
||||
<http://example.org/cartoons#Zuko> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Firebender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#name> "Zuko" <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Waterbender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#name> "Katara" <http://example.org/cartoons> . |
||||
|
||||
Added Quads: |
||||
<http://example.org/cartoons#Katara> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Waterbender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#name> "Katara" <http://example.org/cartoons> . |
||||
|
||||
Removed Quads: |
||||
undefined |
||||
*/ |
||||
subscribableDataset.addAll([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Katara"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Waterbender"), |
||||
namedNode("http://example.org/cartoons"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Katara"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Katara"), |
||||
namedNode("http://example.org/cartoons"), |
||||
), |
||||
]); |
||||
|
||||
/* |
||||
Prints: |
||||
ZUKO NODE CHANGED ============ |
||||
<http://example.org/cartoons#Zuko> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Firebender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#name> "Zuko" <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
|
||||
Added Quads: |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
|
||||
Removed Quads: |
||||
undefined |
||||
|
||||
CARTOON GRAPH CHANGED ============ |
||||
<http://example.org/cartoons#Zuko> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Firebender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#name> "Zuko" <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Waterbender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#name> "Katara" <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
|
||||
Added Quads: |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
|
||||
Removed Quads: |
||||
undefined |
||||
*/ |
||||
subscribableDataset.addAll([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Katara"), |
||||
namedNode("http://example.org/cartoons#hasEnemy"), |
||||
namedNode("http://example.org/cartoons#Zuko"), |
||||
namedNode("http://example.org/cartoons"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Zuko"), |
||||
namedNode("http://example.org/cartoons#hasEnemy"), |
||||
namedNode("http://example.org/cartoons#Katara"), |
||||
namedNode("http://example.org/cartoons"), |
||||
), |
||||
]); |
||||
|
||||
// If there are many operation you want to do at once, use transactions.
|
||||
// An update will not be triggered until the transaction is committed.
|
||||
const transactionalDataset = subscribableDataset.startTransaction(); |
||||
// Delete all triples with a "hasEnemy" predicate
|
||||
transactionalDataset.deleteMatches( |
||||
undefined, |
||||
namedNode("http://example.org/cartoons#hasEnemy"), |
||||
undefined, |
||||
undefined, |
||||
); |
||||
// Add "hasFrient" predicate
|
||||
transactionalDataset.addAll([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Katara"), |
||||
namedNode("http://example.org/cartoons#hasFriend"), |
||||
namedNode("http://example.org/cartoons#Zuko"), |
||||
namedNode("http://example.org/cartoons"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Zuko"), |
||||
namedNode("http://example.org/cartoons#hasFriend"), |
||||
namedNode("http://example.org/cartoons#Katara"), |
||||
namedNode("http://example.org/cartoons"), |
||||
), |
||||
]); |
||||
/* |
||||
Prints: |
||||
ZUKO NODE CHANGED ============ |
||||
<http://example.org/cartoons#Zuko> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Firebender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#name> "Zuko" <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasFriend> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasFriend> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
|
||||
Added Quads: |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasFriend> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasFriend> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
|
||||
Removed Quads: |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
|
||||
CARTOON GRAPH CHANGED ============ |
||||
<http://example.org/cartoons#Zuko> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Firebender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#name> "Zuko" <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasFriend> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Waterbender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#name> "Katara" <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasFriend> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
|
||||
Added Quads: |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasFriend> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasFriend> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
|
||||
Removed Quads: |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
*/ |
||||
transactionalDataset.commit(); |
@ -0,0 +1,6 @@ |
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const sharedConfig = require("../../jest.config.js"); |
||||
module.exports = { |
||||
...sharedConfig, |
||||
rootDir: "./", |
||||
}; |
@ -0,0 +1,50 @@ |
||||
{ |
||||
"name": "@ldo/dataset", |
||||
"version": "0.0.0", |
||||
"description": "An RDFJS dataset implementation", |
||||
"main": "dist/index.js", |
||||
"scripts": { |
||||
"build": "tsc --project tsconfig.build.json", |
||||
"watch": "tsc --watch", |
||||
"test": "jest --coverage", |
||||
"example:extendedDataset": "ts-node ./example/extendedDatasetExample.ts", |
||||
"example:subscribableDataset": "ts-node ./example/subscribableDatasetExample.ts", |
||||
"example:loadData": "ts-node ./example/loadDataExample.ts", |
||||
"prepublishOnly": "npm run test && npm run build", |
||||
"lint": "eslint src/** --fix --no-error-on-unmatched-pattern" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "git+https://github.com/o-development/o-dataset-pack.git" |
||||
}, |
||||
"author": "Jackson Morgan", |
||||
"license": "MIT", |
||||
"bugs": { |
||||
"url": "https://github.com/o-development/o-dataset-pack/issues" |
||||
}, |
||||
"homepage": "https://github.com/o-development/o-dataset-pack#readme", |
||||
"devDependencies": { |
||||
"@rdfjs/types": "^1.0.1", |
||||
"@types/jest": "^27.0.3", |
||||
"@types/jsonld": "^1.5.6", |
||||
"@types/n3": "^1.10.4", |
||||
"@types/rdfjs__dataset": "^1.0.4", |
||||
"@types/readable-stream": "^2.3.13", |
||||
"jest": "^27.4.5", |
||||
"ts-jest": "^27.1.2", |
||||
"ts-node": "^9.1.1" |
||||
}, |
||||
"dependencies": { |
||||
"@rdfjs/data-model": "^1.2.0", |
||||
"@rdfjs/dataset": "^1.1.0", |
||||
"buffer": "^6.0.3", |
||||
"n3": "^1.10.0", |
||||
"readable-stream": "^4.2.0" |
||||
}, |
||||
"overrides": { |
||||
"readable-stream": "^4.2.0" |
||||
}, |
||||
"files": [ |
||||
"dist" |
||||
] |
||||
} |
@ -0,0 +1,395 @@ |
||||
import type { |
||||
DatasetCore, |
||||
Dataset, |
||||
BaseQuad, |
||||
Stream, |
||||
Term, |
||||
DatasetCoreFactory, |
||||
Quad, |
||||
} from "@rdfjs/types"; |
||||
import { Writer } from "n3"; |
||||
import { Readable } from "readable-stream"; |
||||
|
||||
/** |
||||
* A full implementation of the RDF JS Dataset interface. |
||||
*/ |
||||
export default class ExtendedDataset<InAndOutQuad extends BaseQuad = BaseQuad> |
||||
implements Dataset<InAndOutQuad, InAndOutQuad> |
||||
{ |
||||
/** |
||||
* The main backing dataset |
||||
*/ |
||||
protected dataset: DatasetCore<InAndOutQuad, InAndOutQuad>; |
||||
|
||||
/** |
||||
* A factory that generates datasets for the methods |
||||
*/ |
||||
protected datasetCoreFactory: DatasetCoreFactory<InAndOutQuad, InAndOutQuad>; |
||||
|
||||
/** |
||||
* Constructor |
||||
*/ |
||||
constructor( |
||||
dataset: DatasetCore<InAndOutQuad, InAndOutQuad>, |
||||
datasetFactory: DatasetCoreFactory<InAndOutQuad, InAndOutQuad>, |
||||
) { |
||||
this.dataset = dataset; |
||||
this.datasetCoreFactory = datasetFactory; |
||||
} |
||||
|
||||
/** |
||||
* Creates a blank dataset using the dataset factory |
||||
*/ |
||||
private createBlankDataset(): Dataset<InAndOutQuad, InAndOutQuad> { |
||||
return new ExtendedDataset<InAndOutQuad>( |
||||
this.datasetCoreFactory.dataset(), |
||||
this.datasetCoreFactory, |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Imports the quads into this dataset. |
||||
* This method differs from Dataset.union in that it adds all quads to the current instance, rather than combining quads and the current instance to create a new instance. |
||||
* @param quads |
||||
* @returns the dataset instance it was called on. |
||||
*/ |
||||
addAll(quads: InAndOutQuad[] | Dataset<InAndOutQuad>): this { |
||||
for (const quad of quads) { |
||||
this.add(quad); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Returns true if the current instance is a superset of the given dataset; differently put: if the given dataset is a subset of, is contained in the current dataset. |
||||
* Blank Nodes will be normalized. |
||||
* @param other |
||||
*/ |
||||
contains(other: Dataset<InAndOutQuad>): boolean { |
||||
if (other.size > this.size) { |
||||
return false; |
||||
} |
||||
for (const quad of other) { |
||||
if (!this.has(quad)) { |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* This method removes the quads in the current instance that match the given arguments. The logic described in Quad Matching is applied for each quad in this dataset to select the quads which will be deleted. |
||||
* @param subject |
||||
* @param predicate |
||||
* @param object |
||||
* @param graph |
||||
* @returns the dataset instance it was called on. |
||||
*/ |
||||
deleteMatches( |
||||
subject?: Term, |
||||
predicate?: Term, |
||||
object?: Term, |
||||
graph?: Term, |
||||
): this { |
||||
const matching = this.match(subject, predicate, object, graph); |
||||
for (const quad of matching) { |
||||
this.dataset.delete(quad); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Returns a new dataset that contains alls quads from the current dataset, not included in the given dataset. |
||||
* @param other |
||||
*/ |
||||
difference( |
||||
other: DatasetCore<InAndOutQuad>, |
||||
): Dataset<InAndOutQuad, InAndOutQuad> { |
||||
const dataset = this.createBlankDataset(); |
||||
for (const quad of this) { |
||||
if (!other.has(quad)) { |
||||
dataset.add(quad); |
||||
} |
||||
} |
||||
return dataset; |
||||
} |
||||
|
||||
/** |
||||
* Returns true if the current instance contains the same graph structure as the given dataset. |
||||
* @param other |
||||
*/ |
||||
equals(other: Dataset<InAndOutQuad, InAndOutQuad>): boolean { |
||||
if (this.size !== other.size) { |
||||
return false; |
||||
} |
||||
for (const quad of this) { |
||||
if (!other.has(quad)) { |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Universal quantification method, tests whether every quad in the dataset passes the test implemented by the provided iteratee. |
||||
* This method immediately returns boolean false once a quad that does not pass the test is found. |
||||
* This method always returns boolean true on an empty dataset. |
||||
* Note: This method is aligned with Array.prototype.every() in ECMAScript-262. |
||||
* @param iteratee |
||||
*/ |
||||
every(iteratee: (quad: InAndOutQuad, dataset: this) => boolean): boolean { |
||||
for (const quad of this) { |
||||
if (!iteratee(quad, this)) { |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Creates a new dataset with all the quads that pass the test implemented by the provided iteratee. |
||||
* Note: This method is aligned with Array.prototype.filter() in ECMAScript-262. |
||||
* @param iteratee |
||||
*/ |
||||
filter( |
||||
iteratee: (quad: InAndOutQuad, dataset: this) => boolean, |
||||
): Dataset<InAndOutQuad, InAndOutQuad> { |
||||
const dataset = this.createBlankDataset(); |
||||
for (const quad of this) { |
||||
if (iteratee(quad, this)) { |
||||
dataset.add(quad); |
||||
} |
||||
} |
||||
return dataset; |
||||
} |
||||
|
||||
/** |
||||
* Executes the provided iteratee once on each quad in the dataset. |
||||
* Note: This method is aligned with Array.prototype.forEach() in ECMAScript-262. |
||||
* @param iteratee |
||||
*/ |
||||
forEach(iteratee: (quad: InAndOutQuad, dataset: this) => void): void { |
||||
for (const quad of this) { |
||||
iteratee(quad, this); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Imports all quads from the given stream into the dataset. |
||||
* The stream events end and error are wrapped in a Promise. |
||||
* @param stream |
||||
*/ |
||||
import(stream: Stream<InAndOutQuad>): Promise<this> { |
||||
return new Promise((resolve, reject) => { |
||||
stream |
||||
.on("data", (quad) => { |
||||
this.add(quad); |
||||
}) |
||||
.on("end", () => { |
||||
resolve(this); |
||||
}) |
||||
.on("error", (err) => reject(err)); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Returns a new dataset containing alls quads from the current dataset that are also included in the given dataset. |
||||
* @param other |
||||
*/ |
||||
intersection( |
||||
other: Dataset<InAndOutQuad, InAndOutQuad>, |
||||
): Dataset<InAndOutQuad, InAndOutQuad> { |
||||
const dataset = this.createBlankDataset(); |
||||
const iteratingDataset = this.size < other.size ? this : other; |
||||
const comparingDataset = this.size < other.size ? other : this; |
||||
for (const quad of iteratingDataset) { |
||||
if (comparingDataset.has(quad)) { |
||||
dataset.add(quad); |
||||
} |
||||
} |
||||
return dataset; |
||||
} |
||||
|
||||
/** |
||||
* Returns a new dataset containing all quads returned by applying iteratee to each quad in the current dataset. |
||||
* @param iteratee |
||||
*/ |
||||
map( |
||||
iteratee: (quad: InAndOutQuad, dataset: this) => InAndOutQuad, |
||||
): Dataset<InAndOutQuad, InAndOutQuad> { |
||||
const dataset = this.createBlankDataset(); |
||||
for (const quad of this) { |
||||
dataset.add(iteratee(quad, this)); |
||||
} |
||||
return dataset; |
||||
} |
||||
|
||||
/** |
||||
* This method calls the iteratee on each quad of the DatasetCore. The first time the iteratee is called, the accumulator value is the initialValue or, if not given, equals to the first quad of the Dataset. The return value of the iteratee is used as accumulator value for the next calls. |
||||
* This method returns the return value of the last iteratee call. |
||||
* Note: This method is aligned with Array.prototype.reduce() in ECMAScript-262. |
||||
* @param iteratee |
||||
* @param initialValue |
||||
*/ |
||||
reduce<A = unknown>( |
||||
iteratee: (accumulator: A, quad: InAndOutQuad, dataset: this) => A, |
||||
initialValue?: A, |
||||
): A { |
||||
if (this.size === 0 && initialValue == undefined) { |
||||
throw new Error( |
||||
"Cannot reduce an empty Dataset without an initial value.", |
||||
); |
||||
} |
||||
const thisIterator: Iterator<InAndOutQuad> = this[Symbol.iterator](); |
||||
let iteratorResult = thisIterator.next(); |
||||
let accumulatedValue: A = initialValue as A; |
||||
while (!iteratorResult.done) { |
||||
accumulatedValue = iteratee(accumulatedValue, iteratorResult.value, this); |
||||
iteratorResult = thisIterator.next(); |
||||
} |
||||
return accumulatedValue; |
||||
} |
||||
|
||||
/** |
||||
* Existential quantification method, tests whether some quads in the dataset pass the test implemented by the provided iteratee. |
||||
* Note: This method is aligned with Array.prototype.some() in ECMAScript-262. |
||||
* @param iteratee |
||||
* @returns boolean true once a quad that passes the test is found. |
||||
*/ |
||||
some(iteratee: (quad: InAndOutQuad, dataset: this) => boolean): boolean { |
||||
for (const quad of this) { |
||||
if (iteratee(quad, this)) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Returns the set of quads within the dataset as a host language native sequence, for example an Array in ECMAScript-262. |
||||
* Note: Since a DatasetCore is an unordered set, the order of the quads within the returned sequence is arbitrary. |
||||
*/ |
||||
toArray(): InAndOutQuad[] { |
||||
const array: InAndOutQuad[] = []; |
||||
for (const quad of this) { |
||||
array.push(quad); |
||||
} |
||||
return array; |
||||
} |
||||
|
||||
/** |
||||
* Returns an N-Quads string representation of the dataset, preprocessed with RDF Dataset Normalization algorithm. |
||||
*/ |
||||
toCanonical(): string { |
||||
throw new Error("Method not implemented."); |
||||
} |
||||
|
||||
/** |
||||
* Returns a stream that contains all quads of the dataset. |
||||
*/ |
||||
toStream(): Stream<InAndOutQuad> { |
||||
const iterator = this[Symbol.iterator](); |
||||
let curNext = iterator.next(); |
||||
const stream = new Readable({ |
||||
objectMode: true, |
||||
read() { |
||||
if (curNext.done || !curNext.value) { |
||||
this.push(null); |
||||
return; |
||||
} |
||||
this.push(curNext.value); |
||||
curNext = iterator.next(); |
||||
}, |
||||
}); |
||||
return stream; |
||||
} |
||||
|
||||
/** |
||||
* Returns an N-Quads string representation of the dataset. |
||||
* No prior normalization is required, therefore the results for the same quads may vary depending on the Dataset implementation. |
||||
*/ |
||||
toString(): string { |
||||
const writer = new Writer<InAndOutQuad>({ format: "N-Triples" }); |
||||
return writer.quadsToString(this.toArray() as Quad[]); |
||||
} |
||||
|
||||
/** |
||||
* Returns a new Dataset that is a concatenation of this dataset and the quads given as an argument. |
||||
* @param other |
||||
*/ |
||||
union( |
||||
other: Dataset<InAndOutQuad, InAndOutQuad>, |
||||
): Dataset<InAndOutQuad, InAndOutQuad> { |
||||
const dataset = this.createBlankDataset(); |
||||
for (const quad of this) { |
||||
dataset.add(quad); |
||||
} |
||||
for (const quad of other) { |
||||
dataset.add(quad); |
||||
} |
||||
return dataset; |
||||
} |
||||
|
||||
/** |
||||
* This method returns a new dataset that is comprised of all quads in the current instance matching the given arguments. The logic described in Quad Matching is applied for each quad in this dataset to check if it should be included in the output dataset. |
||||
* @param subject |
||||
* @param predicate |
||||
* @param object |
||||
* @param graph |
||||
* @returns a Dataset with matching triples |
||||
*/ |
||||
match( |
||||
subject?: Term | null, |
||||
predicate?: Term | null, |
||||
object?: Term | null, |
||||
graph?: Term | null, |
||||
): Dataset<InAndOutQuad, InAndOutQuad> { |
||||
return new ExtendedDataset( |
||||
this.dataset.match(subject, predicate, object, graph), |
||||
this.datasetCoreFactory, |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* A non-negative integer that specifies the number of quads in the set. |
||||
*/ |
||||
public get size(): number { |
||||
return this.dataset.size; |
||||
} |
||||
|
||||
/** |
||||
* Adds the specified quad to the dataset. |
||||
* Existing quads, as defined in Quad.equals, will be ignored. |
||||
* @param quad |
||||
* @returns the dataset instance it was called on. |
||||
*/ |
||||
public add(quad: InAndOutQuad): this { |
||||
this.dataset.add(quad); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Removes the specified quad from the dataset. |
||||
* This method returns the dataset instance it was called on. |
||||
* @param quad |
||||
*/ |
||||
public delete(quad: InAndOutQuad): this { |
||||
this.dataset.delete(quad); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Determines whether a dataset includes a certain quad, returning true or false as appropriate. |
||||
* @param quad |
||||
*/ |
||||
public has(quad: InAndOutQuad): boolean { |
||||
return Boolean(this.dataset.has(quad)); |
||||
} |
||||
|
||||
/** |
||||
* Returns an iterator |
||||
*/ |
||||
public [Symbol.iterator](): Iterator<InAndOutQuad, InAndOutQuad, undefined> { |
||||
return this.dataset[Symbol.iterator](); |
||||
} |
||||
} |
@ -0,0 +1,34 @@ |
||||
import type { |
||||
DatasetFactory, |
||||
BaseQuad, |
||||
Dataset, |
||||
DatasetCoreFactory, |
||||
} from "@rdfjs/types"; |
||||
import ExtendedDataset from "./ExtendedDataset"; |
||||
|
||||
/** |
||||
* A DatasetFactory that creates an ExtendedDataset given a DatasetCoreFactory. |
||||
*/ |
||||
export default class ExtendedDatasetFactory< |
||||
InAndOutQuad extends BaseQuad = BaseQuad, |
||||
> implements DatasetFactory<InAndOutQuad, InAndOutQuad> |
||||
{ |
||||
private datasetCoreFactory: DatasetCoreFactory<InAndOutQuad, InAndOutQuad>; |
||||
constructor( |
||||
datasetCoreFactory: DatasetCoreFactory<InAndOutQuad, InAndOutQuad>, |
||||
) { |
||||
this.datasetCoreFactory = datasetCoreFactory; |
||||
} |
||||
|
||||
dataset( |
||||
quads?: Dataset<InAndOutQuad, InAndOutQuad> | InAndOutQuad[], |
||||
): ExtendedDataset<InAndOutQuad> { |
||||
return new ExtendedDataset( |
||||
// The typings are incorrect on the dataset core factory
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this.datasetCoreFactory.dataset(quads), |
||||
this.datasetCoreFactory, |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,59 @@ |
||||
import type { Dataset, DatasetFactory, Quad } from "@rdfjs/types"; |
||||
import type { ParserOptions } from "n3"; |
||||
import { Parser } from "n3"; |
||||
// import { Readable } from "readable-stream";
|
||||
// import ParserJsonld from "@rdfjs/parser-jsonld";
|
||||
|
||||
/** |
||||
* Creates a dataset with a string input that could be SON-LD, Turtle, N-Triples, TriG, RDF*, or N3. |
||||
* @param datasetFactory A datasetFactory that will initialize a returned dataset.\ |
||||
* @param data A string representation of RDF Data in JSON-LD, Turtle, N-Triples, TriG, RDF*, or N3. |
||||
* @param options Parser options: { |
||||
* format?: string; |
||||
* factory?: RDF.DataFactory; |
||||
* baseIRI?: string; |
||||
* blankNodePrefix?: string; |
||||
* } |
||||
* @returns A dataset |
||||
*/ |
||||
export default async function createDatasetFromSerializedInput< |
||||
ReturnDataset extends Dataset = Dataset, |
||||
>( |
||||
datasetFactory: DatasetFactory<Quad>, |
||||
data: string, |
||||
options?: ParserOptions, |
||||
): Promise<ReturnDataset> { |
||||
// JSON-LD Parsing
|
||||
if (options && options.format === "application/json-ld") { |
||||
throw new Error("Not Implemented"); |
||||
// return new Promise((resolve, reject) => {
|
||||
// JSON.parse(data);
|
||||
// const parserJsonld = new ParserJsonld();
|
||||
|
||||
// const input = new Readable({
|
||||
// read: () => {
|
||||
// input.push(data);
|
||||
// input.push(null);
|
||||
// },
|
||||
// });
|
||||
|
||||
// const output = parserJsonld.import(input);
|
||||
// const quads: Quad[] = [];
|
||||
// output.on("data", (quad) => {
|
||||
// quads.push(quad);
|
||||
// });
|
||||
// output.on("end", () => {
|
||||
// resolve((datasetFactory.dataset(quads) as unknown) as ReturnDataset);
|
||||
// });
|
||||
// /* istanbul ignore next */
|
||||
// output.on("error", (err) => {
|
||||
// /* istanbul ignore next */
|
||||
// reject(err);
|
||||
// });
|
||||
// });
|
||||
} |
||||
// N3 Parsing
|
||||
const parser = new Parser(options as ParserOptions); |
||||
const quads = parser.parse(data); |
||||
return datasetFactory.dataset(quads) as unknown as ReturnDataset; |
||||
} |
@ -0,0 +1,36 @@ |
||||
import type { |
||||
Dataset, |
||||
DatasetCoreFactory, |
||||
DatasetCore, |
||||
Quad, |
||||
} from "@rdfjs/types"; |
||||
import ExtendedDatasetFactory from "./ExtendedDatasetFactory"; |
||||
import { dataset as initializeDatasetCore } from "@rdfjs/dataset"; |
||||
import type { ExtendedDataset } from "."; |
||||
|
||||
/** |
||||
* Creates a dataset factory that generates ExtendedDatasets |
||||
* @returns DatasetFactory |
||||
*/ |
||||
export function createExtendedDatasetFactory(): ExtendedDatasetFactory<Quad> { |
||||
const datasetFactory: DatasetCoreFactory<Quad> = { |
||||
dataset: (quads?: Dataset<Quad> | Quad[]): DatasetCore<Quad> => { |
||||
return initializeDatasetCore<Quad>( |
||||
Array.isArray(quads) ? quads : quads?.toArray(), |
||||
); |
||||
}, |
||||
}; |
||||
return new ExtendedDatasetFactory<Quad>(datasetFactory); |
||||
} |
||||
|
||||
/** |
||||
* Creates an ExtendedDataset |
||||
* @param quads: A dataset or array of Quads to initialize the dataset. |
||||
* @returns Dataset |
||||
*/ |
||||
export default function createExtendedDataset( |
||||
quads?: Dataset<Quad> | Quad[], |
||||
): ExtendedDataset<Quad> { |
||||
const extendedDatasetFactory = createExtendedDatasetFactory(); |
||||
return extendedDatasetFactory.dataset(quads); |
||||
} |
@ -0,0 +1,28 @@ |
||||
import type { ParserOptions } from "n3"; |
||||
import type { Quad } from "@rdfjs/types"; |
||||
import createDatasetFromSerializedInput from "./createDatasetFromSerializedInput"; |
||||
import { createExtendedDatasetFactory } from "./createExtendedDataset"; |
||||
import type ExtendedDataset from "./ExtendedDataset"; |
||||
|
||||
/** |
||||
* Creates an ExtendedDataset with a string input that could be JSON-LD, Turtle, N-Triples, TriG, RDF*, or N3. |
||||
* @param data A string representation of RDF Data in JSON-LD, Turtle, N-Triples, TriG, RDF*, or N3. |
||||
* @param options Parser options: { |
||||
* format?: string; |
||||
* factory?: RDF.DataFactory; |
||||
* baseIRI?: string; |
||||
* blankNodePrefix?: string; |
||||
* } |
||||
* @returns A dataset |
||||
*/ |
||||
export default async function createExtendedDatasetFromSerializedInput( |
||||
data: string, |
||||
options?: ParserOptions, |
||||
): Promise<ExtendedDataset<Quad>> { |
||||
const datasetFactory = createExtendedDatasetFactory(); |
||||
return createDatasetFromSerializedInput<ExtendedDataset<Quad>>( |
||||
datasetFactory, |
||||
data, |
||||
options, |
||||
); |
||||
} |
@ -0,0 +1,9 @@ |
||||
export { |
||||
default as createDataset, |
||||
createExtendedDatasetFactory as createDatasetFactory, |
||||
} from "./createExtendedDataset"; |
||||
export { default as createDatasetFromSerializedInput } from "./createDatasetFromSerializedInput"; |
||||
export { default as serializedToDataset } from "./createExtendedDatasetFromSerializedInput"; |
||||
|
||||
export { default as ExtendedDataset } from "./ExtendedDataset"; |
||||
export { default as ExtendedDatasetFactory } from "./ExtendedDatasetFactory"; |
@ -0,0 +1,8 @@ |
||||
import { createDataset } from "../src"; |
||||
import testDataset from "./dataset.testHelper"; |
||||
|
||||
describe("ExtendedDataset", () => { |
||||
testDataset({ |
||||
dataset: createDataset, |
||||
}); |
||||
}); |
@ -0,0 +1,12 @@ |
||||
import { createDataset } from "../src"; |
||||
import { quad, namedNode } from "@rdfjs/data-model"; |
||||
|
||||
describe("createExtendedDataset", () => { |
||||
it("creates a dataset when give another datset", () => { |
||||
const dataset1 = createDataset([ |
||||
quad(namedNode("a"), namedNode("b"), namedNode("c")), |
||||
]); |
||||
const dataset2 = createDataset(dataset1); |
||||
expect(dataset1.equals(dataset2)).toBe(true); |
||||
}); |
||||
}); |
@ -0,0 +1,32 @@ |
||||
import { serializedToDataset } from "../src"; |
||||
import { turtleData, jsonLdData, turtleData2 } from "./sampleData"; |
||||
|
||||
describe("createExtendedDatasetFromSerializedInput", () => { |
||||
it("creates a dataset with turtle", async () => { |
||||
const dataset = await serializedToDataset(turtleData); |
||||
expect(dataset.size).toBe(9); |
||||
expect(dataset.toString()).toBe( |
||||
'<#id1604448082795> <http://www.w3.org/2002/12/cal/ical#dtstart> "2020-11-04T00:01:22Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .\n<#id1604448082795> <http://www.w3.org/2005/01/wf/flow#participant> <undefined/profile/card#me> .\n<#id1604448082795> <http://www.w3.org/ns/ui#backgroundColor> "#e1f7cd" .\n<#this> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/ns/pim/meeting#LongChat> .\n<#this> <http://purl.org/dc/elements/1.1/author> <undefined/profile/card#me> .\n<#this> <http://purl.org/dc/elements/1.1/created> "2020-11-04T00:01:20Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .\n<#this> <http://purl.org/dc/elements/1.1/title> "Chat channel" .\n<#this> <http://www.w3.org/2005/01/wf/flow#participation> <#id1604448082795> .\n<#this> <http://www.w3.org/ns/ui#sharedPreferences> <#SharedPreferences> .\n', |
||||
); |
||||
}); |
||||
|
||||
it.skip("creates a dataset with json-ld", async () => { |
||||
const dataset = await serializedToDataset(JSON.stringify(jsonLdData), { |
||||
format: "application/json-ld", |
||||
}); |
||||
expect(dataset.size).toBe(9); |
||||
}); |
||||
|
||||
it("Should create a dataset with some more turtle", async () => { |
||||
const dataset = await serializedToDataset(turtleData2); |
||||
expect(dataset.toString()).toBe( |
||||
'<http://a.example/Employee7> <http://xmlns.com/foaf/0.1/givenName> "Robert" .\n<http://a.example/Employee7> <http://xmlns.com/foaf/0.1/givenName> "Taylor" .\n<http://a.example/Employee7> <http://xmlns.com/foaf/0.1/familyName> "Johnson" .\n<http://a.example/Employee7> <http://www.w3.org/2002/12/cal/ical#dtstart> "2020-11-04T00:01:22Z" .\n<http://a.example/Employee7> <http://xmlns.com/foaf/0.1/mbox> <mailto:rtj@example.com> .\n', |
||||
); |
||||
}); |
||||
|
||||
it.skip("Should error when given invalid JSON", async () => { |
||||
await expect( |
||||
serializedToDataset('{ bad" json', { format: "application/json-ld" }), |
||||
).rejects.toThrow("Unexpected token b in JSON at position 2"); |
||||
}); |
||||
}); |
@ -0,0 +1,650 @@ |
||||
import { namedNode, literal, quad } from "@rdfjs/data-model"; |
||||
import type { Quad_Object, Quad_Predicate } from "n3"; |
||||
import type { |
||||
BaseQuad, |
||||
Dataset, |
||||
Quad_Subject, |
||||
DatasetFactory, |
||||
Quad, |
||||
} from "@rdfjs/types"; |
||||
import { Readable } from "stream"; |
||||
|
||||
export default function testDataset( |
||||
datasetFactory: DatasetFactory<Quad>, |
||||
): void { |
||||
describe("Standard Dataset Test", () => { |
||||
let dataset: Dataset<Quad>; |
||||
|
||||
beforeEach(() => { |
||||
dataset = datasetFactory.dataset(); |
||||
}); |
||||
|
||||
const initializeDataset = () => { |
||||
dataset = datasetFactory.dataset([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Tom"), |
||||
), |
||||
]); |
||||
}; |
||||
|
||||
it("Adds a quad", () => { |
||||
const addedQuad = quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
); |
||||
dataset.add(addedQuad); |
||||
expect(dataset.has(addedQuad)).toBe(true); |
||||
}); |
||||
|
||||
it("Deletes a quad", () => { |
||||
initializeDataset(); |
||||
const deletedQuad = quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
); |
||||
dataset.delete(deletedQuad); |
||||
expect(dataset.has(deletedQuad)).toBe(false); |
||||
}); |
||||
|
||||
it("Checks if it has a quad when it does", () => { |
||||
initializeDataset(); |
||||
const hadQuad = quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
); |
||||
expect(dataset.has(hadQuad)).toBe(true); |
||||
}); |
||||
|
||||
it("Checks if it has a quad when it doesn't", () => { |
||||
initializeDataset(); |
||||
const hadQuad = quad( |
||||
namedNode("http://fake.com"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
); |
||||
expect(dataset.has(hadQuad)).toBe(false); |
||||
}); |
||||
|
||||
it("Can match a quad", () => { |
||||
initializeDataset(); |
||||
const matchDataset = dataset.match( |
||||
null, |
||||
namedNode("http://example.org/cartoons#name"), |
||||
null, |
||||
); |
||||
expect( |
||||
matchDataset.has( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Tom"), |
||||
), |
||||
), |
||||
).toBe(true); |
||||
}); |
||||
|
||||
it("Iterates over itself", () => { |
||||
const quads: Quad[] = []; |
||||
initializeDataset(); |
||||
for (const curQuad of dataset) { |
||||
quads.push(curQuad); |
||||
} |
||||
expect(quads.length).toBe(2); |
||||
}); |
||||
|
||||
it("Adds an array of Quads", () => { |
||||
const quads = [ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Tom"), |
||||
), |
||||
]; |
||||
dataset.addAll(quads); |
||||
expect(dataset.has(quads[0])).toBe(true); |
||||
expect(dataset.has(quads[1])).toBe(true); |
||||
}); |
||||
|
||||
it("Detects if one dataset contains another when it does", () => { |
||||
initializeDataset(); |
||||
const containedDataset = datasetFactory.dataset([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
]); |
||||
expect(dataset.contains(containedDataset)).toBe(true); |
||||
}); |
||||
|
||||
it("Detects if one dataset contains another when it doesn't", () => { |
||||
initializeDataset(); |
||||
const containedDataset = datasetFactory.dataset([ |
||||
quad( |
||||
namedNode("http://fake.com"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
]); |
||||
expect(dataset.contains(containedDataset)).toBe(false); |
||||
}); |
||||
|
||||
it("Datects if one dataset contains another when it does and other is empty", () => { |
||||
initializeDataset(); |
||||
const containedDataset = datasetFactory.dataset(); |
||||
expect(dataset.contains(containedDataset)).toBe(true); |
||||
}); |
||||
|
||||
it("Datects if one dataset contains another when it doesn't and this is empty", () => { |
||||
const containedDataset = datasetFactory.dataset([ |
||||
quad( |
||||
namedNode("http://fake.com"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
]); |
||||
expect(dataset.contains(containedDataset)).toBe(false); |
||||
}); |
||||
|
||||
it("Datects if one dataset contains another when it does and this and other are empty", () => { |
||||
const containedDataset = datasetFactory.dataset(); |
||||
expect(dataset.contains(containedDataset)).toBe(true); |
||||
}); |
||||
|
||||
it("Detects if one dataset contains another when it doesn't", () => { |
||||
initializeDataset(); |
||||
const containedDataset = datasetFactory.dataset([ |
||||
quad( |
||||
namedNode("http://fake.com"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
]); |
||||
expect(dataset.contains(containedDataset)).toBe(false); |
||||
}); |
||||
|
||||
it("Detects if one dataset contains another when the given dataset is larger", () => { |
||||
initializeDataset(); |
||||
const containedDataset = datasetFactory.dataset([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Licky"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tulip"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
]); |
||||
expect(dataset.contains(containedDataset)).toBe(false); |
||||
}); |
||||
|
||||
it("Deletes matching quads", () => { |
||||
initializeDataset(); |
||||
dataset.deleteMatches( |
||||
undefined, |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
undefined, |
||||
); |
||||
expect( |
||||
dataset.has( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
), |
||||
).toBe(false); |
||||
}); |
||||
|
||||
it("Finds the difference between two datasets", () => { |
||||
initializeDataset(); |
||||
const otherDataset = datasetFactory.dataset([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
]); |
||||
const differenceDataset = dataset.difference(otherDataset); |
||||
expect( |
||||
differenceDataset.has( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
), |
||||
).toBe(false); |
||||
expect( |
||||
differenceDataset.has( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Tom"), |
||||
), |
||||
), |
||||
).toBe(true); |
||||
}); |
||||
|
||||
it("Checks if datasets are equal when they are", () => { |
||||
initializeDataset(); |
||||
const otherDataset = datasetFactory.dataset([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Tom"), |
||||
), |
||||
]); |
||||
expect(dataset.equals(otherDataset)).toBe(true); |
||||
}); |
||||
|
||||
it("Checks if datasets are equal when they aren't", () => { |
||||
initializeDataset(); |
||||
const otherDataset = datasetFactory.dataset([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Licky"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Licky"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Tom"), |
||||
), |
||||
]); |
||||
expect(dataset.equals(otherDataset)).toBe(false); |
||||
}); |
||||
|
||||
it("Checks if datasets are equal when they aren't and don't have the same size", () => { |
||||
initializeDataset(); |
||||
const otherDataset = datasetFactory.dataset([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
]); |
||||
expect(dataset.equals(otherDataset)).toBe(false); |
||||
}); |
||||
|
||||
it("Checks if datasets are equal when they aren't and other dataset is empty", () => { |
||||
initializeDataset(); |
||||
const otherDataset = datasetFactory.dataset(); |
||||
expect(dataset.equals(otherDataset)).toBe(false); |
||||
}); |
||||
|
||||
it("Checks if datasets are equal when they aren't and this dataset is empty", () => { |
||||
const otherDataset = datasetFactory.dataset([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
]); |
||||
expect(dataset.equals(otherDataset)).toBe(false); |
||||
}); |
||||
|
||||
it("Checks if datasets are equal when they are and this and other datasets are empty", () => { |
||||
const otherDataset = datasetFactory.dataset(); |
||||
expect(dataset.equals(otherDataset)).toBe(true); |
||||
}); |
||||
|
||||
it("runs the every function when it should return true", () => { |
||||
initializeDataset(); |
||||
expect(dataset.every(() => true)).toBe(true); |
||||
}); |
||||
|
||||
it("runs the every function when it should return false", () => { |
||||
initializeDataset(); |
||||
expect(dataset.every(() => false)).toBe(false); |
||||
}); |
||||
|
||||
it("runs filter", () => { |
||||
initializeDataset(); |
||||
const newDataset = dataset.filter( |
||||
(curQuad) => |
||||
curQuad.predicate.value === |
||||
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type", |
||||
); |
||||
expect(newDataset.size).toBe(1); |
||||
expect( |
||||
newDataset.has( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
), |
||||
).toBe(true); |
||||
}); |
||||
|
||||
it("runs forEach", () => { |
||||
initializeDataset(); |
||||
const quads: BaseQuad[] = []; |
||||
dataset.forEach((curQuad) => { |
||||
quads.push(curQuad); |
||||
}); |
||||
expect( |
||||
quads[0].equals( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
), |
||||
).toBe(true); |
||||
expect( |
||||
quads[1].equals( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Tom"), |
||||
), |
||||
), |
||||
).toBe(true); |
||||
}); |
||||
|
||||
it("Imports quads from a stream", async () => { |
||||
const stream = datasetFactory |
||||
.dataset([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Tom"), |
||||
), |
||||
]) |
||||
.toStream(); |
||||
await dataset.import(stream); |
||||
expect( |
||||
dataset.has( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
), |
||||
).toBe(true); |
||||
expect( |
||||
dataset.has( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Tom"), |
||||
), |
||||
), |
||||
).toBe(true); |
||||
}); |
||||
|
||||
it("Rejects import stream if stream errors", async () => { |
||||
const badStream = new Readable({ |
||||
read() { |
||||
this.emit("error", new Error("This is a bad stream.")); |
||||
}, |
||||
}); |
||||
await expect(dataset.import(badStream)).rejects.toThrow( |
||||
"This is a bad stream.", |
||||
); |
||||
}); |
||||
|
||||
it("finds the intersection when this is bigger", () => { |
||||
initializeDataset(); |
||||
const otherDataset = datasetFactory.dataset([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
]); |
||||
const intersectionDataset = dataset.intersection(otherDataset); |
||||
expect(intersectionDataset.size).toBe(1); |
||||
expect( |
||||
intersectionDataset.has( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
), |
||||
).toBe(true); |
||||
}); |
||||
|
||||
it("finds the intersection when other is bigger", () => { |
||||
initializeDataset(); |
||||
const otherDataset = datasetFactory.dataset([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
quad( |
||||
namedNode("http://fake1.com"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
quad( |
||||
namedNode("http://fake2.com"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
]); |
||||
const intersectionDataset = dataset.intersection(otherDataset); |
||||
expect(intersectionDataset.size).toBe(1); |
||||
expect( |
||||
intersectionDataset.has( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
), |
||||
).toBe(true); |
||||
}); |
||||
|
||||
it("Maps the dataset", () => { |
||||
initializeDataset(); |
||||
const mappedDataset = dataset.map((curQuad) => { |
||||
return quad( |
||||
curQuad.predicate as Quad_Subject, |
||||
curQuad.predicate as Quad_Predicate, |
||||
curQuad.predicate as Quad_Object, |
||||
); |
||||
}); |
||||
expect(mappedDataset.size).toBe(2); |
||||
expect( |
||||
mappedDataset.has( |
||||
quad( |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
), |
||||
), |
||||
).toBe(true); |
||||
expect( |
||||
mappedDataset.has( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#name"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
), |
||||
), |
||||
).toBe(true); |
||||
}); |
||||
|
||||
it("Reduces the dataset", () => { |
||||
initializeDataset(); |
||||
const reducedSubjects = dataset.reduce((agg, curQuad) => { |
||||
return `${agg}${curQuad.subject.value}`; |
||||
}, ""); |
||||
expect(reducedSubjects).toBe( |
||||
"http://example.org/cartoons#Tomhttp://example.org/cartoons#Tom", |
||||
); |
||||
}); |
||||
|
||||
it("Reduces an empty dataset", () => { |
||||
const reducedSubjects = dataset.reduce((agg, curQuad) => { |
||||
return `${agg}${curQuad.subject.value}`; |
||||
}, ""); |
||||
expect(reducedSubjects).toBe(""); |
||||
}); |
||||
|
||||
it("Throws an error if reduce is called on an empty dataset without an initial value", () => { |
||||
expect(() => |
||||
dataset.reduce(() => { |
||||
/* Do nothing */ |
||||
}), |
||||
).toThrow("Cannot reduce an empty Dataset without an initial value."); |
||||
}); |
||||
|
||||
it("Determines of some quad satifies an iteratee when it does", () => { |
||||
initializeDataset(); |
||||
expect( |
||||
dataset.some( |
||||
(curQuad) => |
||||
curQuad.predicate.value === "http://example.org/cartoons#name", |
||||
), |
||||
).toBe(true); |
||||
}); |
||||
|
||||
it("Determines of some quad satifies an iteratee when it doesn't", () => { |
||||
initializeDataset(); |
||||
expect( |
||||
dataset.some( |
||||
(curQuad) => curQuad.predicate.value === "http://fake.com", |
||||
), |
||||
).toBe(false); |
||||
}); |
||||
|
||||
it("Converts the dataset into an array", () => { |
||||
initializeDataset(); |
||||
const arr = dataset.toArray(); |
||||
expect(Array.isArray(arr)).toBe(true); |
||||
expect(arr.length).toBe(2); |
||||
expect(arr[0].predicate.value).toBe( |
||||
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type", |
||||
); |
||||
expect(arr[1].predicate.value).toBe("http://example.org/cartoons#name"); |
||||
}); |
||||
|
||||
it("Converts the empty dataset into an empty array", () => { |
||||
const arr = dataset.toArray(); |
||||
expect(Array.isArray(arr)).toBe(true); |
||||
expect(arr.length).toBe(0); |
||||
}); |
||||
|
||||
it("Throws a not implemented error for toCononical", () => { |
||||
expect(() => dataset.toCanonical()).toThrow("Method not implemented."); |
||||
}); |
||||
|
||||
it("Streams itself", async () => { |
||||
initializeDataset(); |
||||
return new Promise<void>((resolve, reject) => { |
||||
const stream = dataset.toStream(); |
||||
const onDataFunc = jest.fn(); |
||||
stream.on("data", onDataFunc); |
||||
stream.on("error", reject); |
||||
stream.on("end", () => { |
||||
expect(onDataFunc).toBeCalledTimes(2); |
||||
resolve(); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
it("successfully runs toString", () => { |
||||
initializeDataset(); |
||||
const stringified = dataset.toString(); |
||||
expect(stringified).toBe( |
||||
`<http://example.org/cartoons#Tom> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Cat> .\n<http://example.org/cartoons#Tom> <http://example.org/cartoons#name> "Tom" .\n`, |
||||
); |
||||
}); |
||||
|
||||
it("runs toString and gets a compliant N-Triple", () => { |
||||
const dataset = datasetFactory.dataset([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("https://example.org/age"), |
||||
literal("6", "http://www.w3.org/2001/XMLSchema#integer"), |
||||
), |
||||
]); |
||||
expect(dataset.toString()).toBe( |
||||
'<http://example.org/cartoons#Tom> <https://example.org/age> "6"^^<http://www.w3.org/2001/XMLSchema#integer> .\n', |
||||
); |
||||
}); |
||||
|
||||
it("Finds a union", () => { |
||||
initializeDataset(); |
||||
const otherDataset = datasetFactory.dataset([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Licky"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
]); |
||||
const unionDataset = dataset.union(otherDataset); |
||||
expect(unionDataset.size).toBe(3); |
||||
expect( |
||||
unionDataset.has( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Licky"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
), |
||||
).toBe(true); |
||||
expect( |
||||
unionDataset.has( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
), |
||||
).toBe(true); |
||||
expect( |
||||
unionDataset.has( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Tom"), |
||||
), |
||||
), |
||||
).toBe(true); |
||||
}); |
||||
}); |
||||
} |
@ -0,0 +1,19 @@ |
||||
import { |
||||
createDataset, |
||||
createDatasetFactory, |
||||
createDatasetFromSerializedInput, |
||||
serializedToDataset, |
||||
ExtendedDataset, |
||||
ExtendedDatasetFactory, |
||||
} from "../src"; |
||||
|
||||
describe("Exports", () => { |
||||
it("Has all exports", () => { |
||||
expect(createDataset); |
||||
expect(ExtendedDataset); |
||||
expect(ExtendedDatasetFactory); |
||||
expect(serializedToDataset); |
||||
expect(createDatasetFactory); |
||||
expect(createDatasetFromSerializedInput); |
||||
}); |
||||
}); |
@ -0,0 +1,101 @@ |
||||
export const turtleData = ` |
||||
@prefix : <#>. |
||||
@prefix mee: <http://www.w3.org/ns/pim/meeting#>. |
||||
@prefix XML: <http://www.w3.org/2001/XMLSchema#>. |
||||
@prefix n5: <http://purl.org/dc/elements/1.1/>. |
||||
@prefix c6: </profile/card#>. |
||||
@prefix ui: <http://www.w3.org/ns/ui#>. |
||||
@prefix ic: <http://www.w3.org/2002/12/cal/ical#>. |
||||
@prefix flow: <http://www.w3.org/2005/01/wf/flow#>. |
||||
|
||||
:id1604448082795 |
||||
ic:dtstart "2020-11-04T00:01:22Z"^^XML:dateTime; |
||||
flow:participant c6:me; |
||||
ui:backgroundColor "#e1f7cd". |
||||
:this |
||||
a mee:LongChat; |
||||
n5:author c6:me; |
||||
n5:created "2020-11-04T00:01:20Z"^^XML:dateTime; |
||||
n5:title "Chat channel"; |
||||
flow:participation :id1604448082795; |
||||
ui:sharedPreferences :SharedPreferences. |
||||
`;
|
||||
|
||||
export const turtleData2 = ` |
||||
@prefix foaf: <http://xmlns.com/foaf/0.1/> . |
||||
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . |
||||
@prefix ic: <http://www.w3.org/2002/12/cal/ical#>. |
||||
<http://a.example/Employee7> |
||||
foaf:givenName "Robert"^^xsd:string, "Taylor"^^xsd:string ; |
||||
foaf:familyName "Johnson"^^xsd:string ; |
||||
ic:dtstart "2020-11-04T00:01:22Z"^^xsd:string ; |
||||
# no phone number needed |
||||
foaf:mbox <mailto:rtj@example.com> |
||||
. |
||||
`;
|
||||
|
||||
export const jsonLdData = [ |
||||
{ |
||||
"@id": "http://www.w3.org/ns/pim/meeting#LongChat", |
||||
}, |
||||
{ |
||||
"@id": |
||||
"https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#SharedPreferences", |
||||
}, |
||||
{ |
||||
"@id": |
||||
"https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#id1604448082795", |
||||
"http://www.w3.org/2002/12/cal/ical#dtstart": [ |
||||
{ |
||||
"@value": "2020-11-04T00:01:22Z", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#dateTime", |
||||
}, |
||||
], |
||||
"http://www.w3.org/2005/01/wf/flow#participant": [ |
||||
{ |
||||
"@id": "https://jackson.solidcommunity.net/profile/card#me", |
||||
}, |
||||
], |
||||
"http://www.w3.org/ns/ui#backgroundColor": [ |
||||
{ |
||||
"@value": "#e1f7cd", |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
"@id": |
||||
"https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#this", |
||||
"@type": ["http://www.w3.org/ns/pim/meeting#LongChat"], |
||||
"http://purl.org/dc/elements/1.1/author": [ |
||||
{ |
||||
"@id": "https://jackson.solidcommunity.net/profile/card#me", |
||||
}, |
||||
], |
||||
"http://purl.org/dc/elements/1.1/created": [ |
||||
{ |
||||
"@value": "2020-11-04T00:01:20Z", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#dateTime", |
||||
}, |
||||
], |
||||
"http://purl.org/dc/elements/1.1/title": [ |
||||
{ |
||||
"@value": "Chat channel", |
||||
}, |
||||
], |
||||
"http://www.w3.org/2005/01/wf/flow#participation": [ |
||||
{ |
||||
"@id": |
||||
"https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#id1604448082795", |
||||
}, |
||||
], |
||||
"http://www.w3.org/ns/ui#sharedPreferences": [ |
||||
{ |
||||
"@id": |
||||
"https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#SharedPreferences", |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
"@id": "https://jackson.solidcommunity.net/profile/card#me", |
||||
}, |
||||
]; |
@ -0,0 +1,7 @@ |
||||
{ |
||||
"extends": "../../tsconfig.base.json", |
||||
"compilerOptions": { |
||||
"outDir": "./dist" |
||||
}, |
||||
"include": ["./src"] |
||||
} |
@ -0,0 +1,3 @@ |
||||
{ |
||||
"extends": ["../../.eslintrc"] |
||||
} |
@ -0,0 +1,65 @@ |
||||
import { createDataset } from "../src"; |
||||
import { quad, namedNode, literal } from "@rdfjs/data-model"; |
||||
// Required for advanced features:
|
||||
import { dataset as initializeDatasetCore } from "@rdfjs/dataset"; |
||||
import { ExtendedDatasetFactory } from "../src"; |
||||
import type { |
||||
Dataset, |
||||
Quad, |
||||
DatasetCoreFactory, |
||||
DatasetCore, |
||||
} from "@rdfjs/types"; |
||||
|
||||
/** |
||||
* Create a dataset with default settings |
||||
*/ |
||||
const defaultDataset = createDataset(); |
||||
|
||||
/** |
||||
* Create a dataset with default settings and initialized values |
||||
*/ |
||||
const initializedQuads = [ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Tom"), |
||||
), |
||||
]; |
||||
const defaultDataset2 = createDataset(initializedQuads); |
||||
|
||||
/** |
||||
* (Advanced Feature) Create a dataset by injecting a chosen datasetCore and datasetCoreFactory |
||||
*/ |
||||
const datasetFactory: DatasetCoreFactory = { |
||||
dataset: (quads?: Dataset<Quad> | Quad[]): DatasetCore => { |
||||
return initializeDatasetCore( |
||||
Array.isArray(quads) ? quads : quads?.toArray(), |
||||
); |
||||
}, |
||||
}; |
||||
const extendedDatasetFactory = new ExtendedDatasetFactory(datasetFactory); |
||||
const customDataset = extendedDatasetFactory.dataset(initializedQuads); |
||||
|
||||
/** |
||||
* Do all the methods of the RDFJS Dataset interface. For a full list of methods, go to |
||||
* https://rdf.js.org/dataset-spec/#data-interfaces
|
||||
*/ |
||||
defaultDataset.add( |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Miuki"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
), |
||||
); |
||||
const combinedDataset = defaultDataset.union(defaultDataset2); |
||||
const differenceDataset = combinedDataset.difference(customDataset); |
||||
// Prints true because "defaultDataset2" and "customDataset" have equal values
|
||||
// combinedDataset = defaultDataset ∪ defaultDataset2
|
||||
// differenceDatasset = defaultDataset \ customDataset
|
||||
// Therefore differenceDataset == defaultDataset
|
||||
console.log(differenceDataset.equals(defaultDataset)); |
@ -0,0 +1,45 @@ |
||||
import { serializedToDataset, serializedToSubscribableDataset } from "../src"; |
||||
|
||||
async function run(): Promise<void> { |
||||
// Create an ExtendedDataset using Turtle
|
||||
const turtleData = ` |
||||
@prefix : <#>. |
||||
@prefix elem: <http://purl.org/dc/elements/1.1/>. |
||||
@prefix card: </profile/card#>. |
||||
|
||||
:this |
||||
elem:author card:me. |
||||
`;
|
||||
const turtleDataset = await serializedToDataset(turtleData, { |
||||
baseIRI: |
||||
"https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#", |
||||
// NOTE: the "format" field isn't required because Turtle is the default parser
|
||||
}); |
||||
|
||||
// Create a SubcribableDataset using JSON-LD
|
||||
const jsonLdData = [ |
||||
{ |
||||
"@id": |
||||
"https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#this", |
||||
"http://purl.org/dc/elements/1.1/author": [ |
||||
{ |
||||
"@id": "https://jackson.solidcommunity.net/profile/card#me", |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
"@id": "https://jackson.solidcommunity.net/profile/card#me", |
||||
}, |
||||
]; |
||||
const jsonLdDataset = await serializedToSubscribableDataset( |
||||
JSON.stringify(jsonLdData), |
||||
{ |
||||
baseIRI: |
||||
"https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#", |
||||
format: "application/json-ld", |
||||
}, |
||||
); |
||||
// Returns true because the input data describes the same triple.
|
||||
console.log(turtleDataset.equals(jsonLdDataset)); |
||||
} |
||||
run(); |
@ -0,0 +1,187 @@ |
||||
import type { DatasetChanges } from "../src"; |
||||
import { createSubscribableDataset } from "../src"; |
||||
import { quad, namedNode, literal } from "@rdfjs/data-model"; |
||||
import type { Dataset } from "@rdfjs/types"; |
||||
|
||||
// Create an empty subscribable dataset
|
||||
const subscribableDataset = createSubscribableDataset(); |
||||
// Add some initial quads
|
||||
subscribableDataset.addAll([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Zuko"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Firebender"), |
||||
namedNode("http://example.org/cartoons"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Zuko"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Zuko"), |
||||
namedNode("http://example.org/cartoons"), |
||||
), |
||||
]); |
||||
// Set up listeners
|
||||
// Listener that will trigger whenever a quad containing the named
|
||||
// node "http://example.org/cartoons#Zuko" is added or removed.
|
||||
subscribableDataset.on( |
||||
namedNode("http://example.org/cartoons#Zuko"), |
||||
(zukoQuads: Dataset, changes: DatasetChanges) => { |
||||
console.log("ZUKO NODE CHANGED ============"); |
||||
console.log(zukoQuads.toString()); |
||||
console.log("Added Quads:"); |
||||
console.log(changes.added?.toString()); |
||||
console.log("Removed Quads:"); |
||||
console.log(changes.removed?.toString()); |
||||
console.log("\n\n"); |
||||
}, |
||||
); |
||||
// Listener that will trigger whenever a quad containing the named
|
||||
// node "http://example.org/cartoons" is added or removed. This is
|
||||
// useful for keeping track of the cartoons graph.
|
||||
subscribableDataset.on( |
||||
namedNode("http://example.org/cartoons"), |
||||
(cartoonGraphQuads: Dataset, changes: DatasetChanges) => { |
||||
console.log("CARTOON GRAPH CHANGED ============"); |
||||
console.log(cartoonGraphQuads.toString()); |
||||
console.log("Added Quads:"); |
||||
console.log(changes.added?.toString()); |
||||
console.log("Removed Quads:"); |
||||
console.log(changes.removed?.toString()); |
||||
console.log("\n\n"); |
||||
}, |
||||
); |
||||
|
||||
// Modify the dataset
|
||||
/* |
||||
Prints: |
||||
CARTOON GRAPH CHANGED ============ |
||||
<http://example.org/cartoons#Zuko> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Firebender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#name> "Zuko" <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Waterbender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#name> "Katara" <http://example.org/cartoons> . |
||||
|
||||
Added Quads: |
||||
<http://example.org/cartoons#Katara> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Waterbender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#name> "Katara" <http://example.org/cartoons> . |
||||
|
||||
Removed Quads: |
||||
undefined |
||||
*/ |
||||
subscribableDataset.addAll([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Katara"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Waterbender"), |
||||
namedNode("http://example.org/cartoons"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Katara"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Katara"), |
||||
namedNode("http://example.org/cartoons"), |
||||
), |
||||
]); |
||||
|
||||
/* |
||||
Prints: |
||||
ZUKO NODE CHANGED ============ |
||||
<http://example.org/cartoons#Zuko> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Firebender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#name> "Zuko" <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
|
||||
Added Quads: |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
|
||||
Removed Quads: |
||||
undefined |
||||
|
||||
CARTOON GRAPH CHANGED ============ |
||||
<http://example.org/cartoons#Zuko> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Firebender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#name> "Zuko" <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Waterbender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#name> "Katara" <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
|
||||
Added Quads: |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
|
||||
Removed Quads: |
||||
undefined |
||||
*/ |
||||
subscribableDataset.addAll([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Katara"), |
||||
namedNode("http://example.org/cartoons#hasEnemy"), |
||||
namedNode("http://example.org/cartoons#Zuko"), |
||||
namedNode("http://example.org/cartoons"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Zuko"), |
||||
namedNode("http://example.org/cartoons#hasEnemy"), |
||||
namedNode("http://example.org/cartoons#Katara"), |
||||
namedNode("http://example.org/cartoons"), |
||||
), |
||||
]); |
||||
|
||||
// If there are many operation you want to do at once, use transactions.
|
||||
// An update will not be triggered until the transaction is committed.
|
||||
const transactionalDataset = subscribableDataset.startTransaction(); |
||||
// Delete all triples with a "hasEnemy" predicate
|
||||
transactionalDataset.deleteMatches( |
||||
undefined, |
||||
namedNode("http://example.org/cartoons#hasEnemy"), |
||||
undefined, |
||||
undefined, |
||||
); |
||||
// Add "hasFrient" predicate
|
||||
transactionalDataset.addAll([ |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Katara"), |
||||
namedNode("http://example.org/cartoons#hasFriend"), |
||||
namedNode("http://example.org/cartoons#Zuko"), |
||||
namedNode("http://example.org/cartoons"), |
||||
), |
||||
quad( |
||||
namedNode("http://example.org/cartoons#Zuko"), |
||||
namedNode("http://example.org/cartoons#hasFriend"), |
||||
namedNode("http://example.org/cartoons#Katara"), |
||||
namedNode("http://example.org/cartoons"), |
||||
), |
||||
]); |
||||
/* |
||||
Prints: |
||||
ZUKO NODE CHANGED ============ |
||||
<http://example.org/cartoons#Zuko> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Firebender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#name> "Zuko" <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasFriend> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasFriend> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
|
||||
Added Quads: |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasFriend> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasFriend> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
|
||||
Removed Quads: |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
|
||||
CARTOON GRAPH CHANGED ============ |
||||
<http://example.org/cartoons#Zuko> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Firebender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#name> "Zuko" <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasFriend> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/cartoons#Waterbender> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#name> "Katara" <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasFriend> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
|
||||
Added Quads: |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasFriend> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasFriend> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
|
||||
Removed Quads: |
||||
<http://example.org/cartoons#Katara> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Zuko> <http://example.org/cartoons> . |
||||
<http://example.org/cartoons#Zuko> <http://example.org/cartoons#hasEnemy> <http://example.org/cartoons#Katara> <http://example.org/cartoons> . |
||||
*/ |
||||
transactionalDataset.commit(); |
@ -0,0 +1,6 @@ |
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const sharedConfig = require("../../jest.config.js"); |
||||
module.exports = { |
||||
...sharedConfig, |
||||
rootDir: "./", |
||||
}; |
@ -0,0 +1,51 @@ |
||||
{ |
||||
"name": "@ldo/subscribable-dataset", |
||||
"version": "0.0.0", |
||||
"description": "An RDFJS dataset implementation that can be subscribed to for updates", |
||||
"main": "dist/index.js", |
||||
"scripts": { |
||||
"build": "tsc --project tsconfig.build.json", |
||||
"watch": "tsc --watch", |
||||
"test": "jest --coverage", |
||||
"example:extendedDataset": "ts-node ./example/extendedDatasetExample.ts", |
||||
"example:subscribableDataset": "ts-node ./example/subscribableDatasetExample.ts", |
||||
"example:loadData": "ts-node ./example/loadDataExample.ts", |
||||
"prepublishOnly": "npm run test && npm run build", |
||||
"lint": "eslint src/** --fix --no-error-on-unmatched-pattern" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "git+https://github.com/o-development/o-dataset-pack.git" |
||||
}, |
||||
"author": "Jackson Morgan", |
||||
"license": "MIT", |
||||
"bugs": { |
||||
"url": "https://github.com/o-development/o-dataset-pack/issues" |
||||
}, |
||||
"homepage": "https://github.com/o-development/o-dataset-pack#readme", |
||||
"devDependencies": { |
||||
"@rdfjs/types": "^1.0.1", |
||||
"@types/jest": "^27.0.3", |
||||
"@types/jsonld": "^1.5.6", |
||||
"@types/n3": "^1.10.4", |
||||
"@types/rdfjs__dataset": "^1.0.4", |
||||
"@types/readable-stream": "^2.3.13", |
||||
"jest": "^27.4.5", |
||||
"ts-jest": "^27.1.2", |
||||
"ts-node": "^9.1.1" |
||||
}, |
||||
"dependencies": { |
||||
"@ldo/dataset": "^0.0.0", |
||||
"@rdfjs/data-model": "^1.2.0", |
||||
"@rdfjs/dataset": "^1.1.0", |
||||
"buffer": "^6.0.3", |
||||
"n3": "^1.10.0", |
||||
"readable-stream": "^4.2.0" |
||||
}, |
||||
"overrides": { |
||||
"readable-stream": "^4.2.0" |
||||
}, |
||||
"files": [ |
||||
"dist" |
||||
] |
||||
} |
@ -0,0 +1,350 @@ |
||||
import type { Dataset, BaseQuad, Term, DatasetFactory } from "@rdfjs/types"; |
||||
import type { |
||||
BulkEditableDataset, |
||||
DatasetChanges, |
||||
TransactionalDataset, |
||||
} from "./types"; |
||||
import { ExtendedDataset } from "@ldo/dataset"; |
||||
|
||||
/** |
||||
* Proxy Transactional Dataset is a transactional dataset that does not duplicate |
||||
* the parent dataset, it will dynamically determine the correct return value for |
||||
* methods in real time when the method is called. |
||||
*/ |
||||
export default class ProxyTransactionalDataset< |
||||
InAndOutQuad extends BaseQuad = BaseQuad, |
||||
> |
||||
extends ExtendedDataset<InAndOutQuad> |
||||
implements |
||||
BulkEditableDataset<InAndOutQuad>, |
||||
TransactionalDataset<InAndOutQuad> |
||||
{ |
||||
/** |
||||
* The parent dataset that will be updated upon commit |
||||
*/ |
||||
private parentDataset: Dataset<InAndOutQuad, InAndOutQuad>; |
||||
|
||||
/** |
||||
* A factory for creating new datasets to be added to the update method |
||||
*/ |
||||
private datasetFactory: DatasetFactory<InAndOutQuad, InAndOutQuad>; |
||||
|
||||
/** |
||||
* The changes made that are ready to commit |
||||
*/ |
||||
private datasetChanges: DatasetChanges<InAndOutQuad>; |
||||
|
||||
/** |
||||
* A list of changes made to the parent dataset upon commit used for rolling back changes. |
||||
* This is different from 'datasetChanges' because datasetChanges is allowed to overlap |
||||
* with the parent dataset. |
||||
* For example, the parent dataset may already have triple A, and datasetChanges can |
||||
* also have triple A. |
||||
*/ |
||||
private committedDatasetChanges?: DatasetChanges<InAndOutQuad>; |
||||
|
||||
/** |
||||
* Constructor |
||||
* @param parentDataset The dataset that will be updated upon commit |
||||
*/ |
||||
constructor( |
||||
parentDataset: Dataset<InAndOutQuad, InAndOutQuad>, |
||||
datasetFactory: DatasetFactory<InAndOutQuad, InAndOutQuad>, |
||||
) { |
||||
super(datasetFactory.dataset(), datasetFactory); |
||||
this.parentDataset = parentDataset; |
||||
this.datasetFactory = datasetFactory; |
||||
this.datasetChanges = {}; |
||||
} |
||||
|
||||
/** |
||||
* ================================================================== |
||||
* DATASET METHODS |
||||
* ================================================================== |
||||
*/ |
||||
|
||||
/** |
||||
* Imports the quads into this dataset. |
||||
* This method differs from Dataset.union in that it adds all quads to the current instance, rather than combining quads and the current instance to create a new instance. |
||||
* @param quads |
||||
* @returns the dataset instance it was called on. |
||||
*/ |
||||
public addAll( |
||||
quads: Dataset<InAndOutQuad, InAndOutQuad> | InAndOutQuad[], |
||||
): this { |
||||
this.updateDatasetChanges({ added: quads }); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Bulk add and remove triples |
||||
* @param changed |
||||
*/ |
||||
public bulk(changes: DatasetChanges<InAndOutQuad>): this { |
||||
this.updateDatasetChanges(changes); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* This method removes the quads in the current instance that match the given arguments. The logic described in Quad Matching is applied for each quad in this dataset to select the quads which will be deleted. |
||||
* @param subject |
||||
* @param predicate |
||||
* @param object |
||||
* @param graph |
||||
* @returns the dataset instance it was called on. |
||||
*/ |
||||
deleteMatches( |
||||
subject?: Term, |
||||
predicate?: Term, |
||||
object?: Term, |
||||
graph?: Term, |
||||
): this { |
||||
this.checkIfTransactionCommitted(); |
||||
const matching = this.match(subject, predicate, object, graph); |
||||
for (const quad of matching) { |
||||
this.delete(quad); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* This method returns a new dataset that is comprised of all quads in the current instance matching the given arguments. The logic described in Quad Matching is applied for each quad in this dataset to check if it should be included in the output dataset. |
||||
* @param subject |
||||
* @param predicate |
||||
* @param object |
||||
* @param graph |
||||
* @returns a Dataset with matching triples |
||||
*/ |
||||
public match( |
||||
subject?: Term | null, |
||||
predicate?: Term | null, |
||||
object?: Term | null, |
||||
graph?: Term | null, |
||||
): Dataset<InAndOutQuad, InAndOutQuad> { |
||||
let finalMatch = this.parentDataset.match( |
||||
subject, |
||||
predicate, |
||||
object, |
||||
graph, |
||||
); |
||||
if (this.datasetChanges.removed) { |
||||
finalMatch = finalMatch.difference(this.datasetChanges.removed); |
||||
} |
||||
if (this.datasetChanges.added) { |
||||
finalMatch = finalMatch.union( |
||||
this.datasetChanges.added.match(subject, predicate, object, graph), |
||||
); |
||||
} |
||||
return finalMatch; |
||||
} |
||||
|
||||
/** |
||||
* A non-negative integer that specifies the number of quads in the set. |
||||
*/ |
||||
public get size(): number { |
||||
return ( |
||||
this.parentDataset.size + |
||||
(this.datasetChanges.added?.difference(this.parentDataset).size || 0) - |
||||
(this.datasetChanges.removed?.intersection(this.parentDataset).size || 0) |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Adds the specified quad to the dataset. |
||||
* Existing quads, as defined in Quad.equals, will be ignored. |
||||
* @param quad |
||||
* @returns the dataset instance it was called on. |
||||
*/ |
||||
public add(quad: InAndOutQuad): this { |
||||
this.updateDatasetChanges({ added: [quad] }); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Removes the specified quad from the dataset. |
||||
* This method returns the dataset instance it was called on. |
||||
* @param quad |
||||
*/ |
||||
public delete(quad: InAndOutQuad): this { |
||||
this.updateDatasetChanges({ removed: [quad] }); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Determines whether a dataset includes a certain quad, returning true or false as appropriate. |
||||
* @param quad |
||||
*/ |
||||
public has(quad: InAndOutQuad): boolean { |
||||
return ( |
||||
!this.datasetChanges.removed?.has(quad) && |
||||
(this.datasetChanges.added?.has(quad) || this.parentDataset.has(quad)) |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Returns an iterator |
||||
*/ |
||||
public [Symbol.iterator](): Iterator<InAndOutQuad> { |
||||
const addedIterator = (this.datasetChanges.added || [])[Symbol.iterator](); |
||||
let addedNext = addedIterator.next(); |
||||
const parentIterator = this.parentDataset[Symbol.iterator](); |
||||
let parentNext = parentIterator.next(); |
||||
return { |
||||
next: () => { |
||||
if (!addedNext || !addedNext.done) { |
||||
const toReturn = addedNext; |
||||
addedNext = addedIterator.next(); |
||||
return toReturn; |
||||
} |
||||
while (!parentNext.done) { |
||||
const toReturn = parentNext; |
||||
parentNext = parentIterator.next(); |
||||
if ( |
||||
!( |
||||
this.datasetChanges.added && |
||||
this.datasetChanges.added.has(toReturn.value) |
||||
) && |
||||
!( |
||||
this.datasetChanges.removed && |
||||
this.datasetChanges.removed.has(toReturn.value) |
||||
) |
||||
) { |
||||
return toReturn; |
||||
} |
||||
} |
||||
return { value: undefined, done: true }; |
||||
}, |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* ================================================================== |
||||
* TANSACTIONAL METHODS |
||||
* ================================================================== |
||||
*/ |
||||
|
||||
/** |
||||
* Checks if the transaction has been committed and throws an error if it has |
||||
* @param changed |
||||
*/ |
||||
private checkIfTransactionCommitted() { |
||||
if (this.committedDatasetChanges) { |
||||
throw new Error("Transaction has already committed"); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Helper method to update the changes made |
||||
* @param changes |
||||
*/ |
||||
private updateDatasetChanges(changes: { |
||||
added?: Dataset<InAndOutQuad> | InAndOutQuad[]; |
||||
removed?: Dataset<InAndOutQuad> | InAndOutQuad[]; |
||||
}): void { |
||||
this.checkIfTransactionCommitted(); |
||||
|
||||
// Add added
|
||||
if (changes.added) { |
||||
if (this.datasetChanges.added) { |
||||
this.datasetChanges.added.addAll(changes.added); |
||||
} else { |
||||
this.datasetChanges.added = this.datasetFactory.dataset(changes.added); |
||||
} |
||||
} |
||||
// Add removed
|
||||
if (changes.removed) { |
||||
if (this.datasetChanges.removed) { |
||||
this.datasetChanges.removed.addAll(changes.removed); |
||||
} else { |
||||
this.datasetChanges.removed = this.datasetFactory.dataset( |
||||
changes.removed, |
||||
); |
||||
} |
||||
} |
||||
|
||||
// Remove duplicates between the two datasets
|
||||
if (this.datasetChanges.added && this.datasetChanges.removed) { |
||||
const changesIntersection = this.datasetChanges.added.intersection( |
||||
this.datasetChanges.removed, |
||||
); |
||||
if (changesIntersection.size > 0) { |
||||
this.datasetChanges.added = |
||||
this.datasetChanges.added.difference(changesIntersection); |
||||
this.datasetChanges.removed = |
||||
this.datasetChanges.removed.difference(changesIntersection); |
||||
} |
||||
} |
||||
|
||||
// Make undefined if size is zero
|
||||
if (this.datasetChanges.added && this.datasetChanges.added.size === 0) { |
||||
this.datasetChanges.added = undefined; |
||||
} |
||||
if (this.datasetChanges.removed && this.datasetChanges.removed.size === 0) { |
||||
this.datasetChanges.removed = undefined; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Helper method to update the parent dataset or any other provided dataset |
||||
*/ |
||||
private updateParentDataset(datasetChanges: DatasetChanges<InAndOutQuad>) { |
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if ((this.parentDataset as any).bulk) { |
||||
(this.parentDataset as BulkEditableDataset<InAndOutQuad>).bulk( |
||||
datasetChanges, |
||||
); |
||||
} else { |
||||
if (datasetChanges.added) { |
||||
this.parentDataset.addAll(datasetChanges.added); |
||||
} |
||||
if (datasetChanges.removed) { |
||||
datasetChanges.removed.forEach((curQuad) => { |
||||
this.parentDataset.delete(curQuad); |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Commits changes made to the parent dataset |
||||
*/ |
||||
public commit(): void { |
||||
this.checkIfTransactionCommitted(); |
||||
this.committedDatasetChanges = { |
||||
added: this.datasetChanges.added?.difference(this.parentDataset), |
||||
removed: this.datasetChanges.removed?.intersection(this.parentDataset), |
||||
}; |
||||
this.updateParentDataset(this.committedDatasetChanges); |
||||
} |
||||
|
||||
/** |
||||
* Rolls back changes made to the parent dataset |
||||
*/ |
||||
public rollback(): void { |
||||
if (!this.committedDatasetChanges) { |
||||
throw new Error( |
||||
"Cannot rollback. Transaction has not yet been committed", |
||||
); |
||||
} |
||||
this.updateParentDataset({ |
||||
added: this.committedDatasetChanges.removed, |
||||
removed: this.committedDatasetChanges.added, |
||||
}); |
||||
this.committedDatasetChanges = undefined; |
||||
} |
||||
|
||||
/** |
||||
* Starts a new transaction with this transactional dataset as the parent |
||||
* @returns |
||||
*/ |
||||
public startTransaction(): TransactionalDataset<InAndOutQuad> { |
||||
// This is caused by the typings being incorrect for the intersect method
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
return new ProxyTransactionalDataset(this, this.datasetFactory); |
||||
} |
||||
|
||||
public getChanges(): DatasetChanges<InAndOutQuad> { |
||||
return this.datasetChanges; |
||||
} |
||||
} |
@ -0,0 +1,646 @@ |
||||
import { EventEmitter } from "events"; |
||||
import type { |
||||
Dataset, |
||||
BaseQuad, |
||||
Stream, |
||||
Term, |
||||
DatasetFactory, |
||||
} from "@rdfjs/types"; |
||||
import { namedNode, blankNode, defaultGraph } from "@rdfjs/data-model"; |
||||
import type { |
||||
SubscribableTerms, |
||||
DatasetChanges, |
||||
nodeEventListener, |
||||
SubscribableDataset, |
||||
TransactionalDataset, |
||||
} from "./types"; |
||||
import ProxyTransactionalDataset from "./ProxyTransactionalDataset"; |
||||
|
||||
/** |
||||
* A wrapper for a dataset that allows subscriptions to be made on nodes to |
||||
* be triggered whenever a quad containing that added or removed. |
||||
*/ |
||||
export default class WrapperSubscribableDataset< |
||||
InAndOutQuad extends BaseQuad = BaseQuad, |
||||
> implements SubscribableDataset<InAndOutQuad> |
||||
{ |
||||
/** |
||||
* The underlying dataset factory |
||||
*/ |
||||
private datasetFactory: DatasetFactory<InAndOutQuad, InAndOutQuad>; |
||||
/** |
||||
* The underlying dataset |
||||
*/ |
||||
private dataset: Dataset<InAndOutQuad, InAndOutQuad>; |
||||
/** |
||||
* The underlying event emitter |
||||
*/ |
||||
private eventEmitter: EventEmitter; |
||||
|
||||
/** |
||||
* |
||||
* @param datasetFactory |
||||
* @param initialDataset |
||||
*/ |
||||
constructor( |
||||
datasetFactory: DatasetFactory<InAndOutQuad, InAndOutQuad>, |
||||
initialDataset?: Dataset<InAndOutQuad, InAndOutQuad>, |
||||
) { |
||||
this.datasetFactory = datasetFactory; |
||||
this.dataset = initialDataset || this.datasetFactory.dataset(); |
||||
this.eventEmitter = new EventEmitter(); |
||||
} |
||||
|
||||
/** |
||||
* ================================================================== |
||||
* DATASET METHODS |
||||
* ================================================================== |
||||
*/ |
||||
|
||||
/** |
||||
* Imports the quads into this dataset. |
||||
* This method differs from Dataset.union in that it adds all quads to the current instance, rather than combining quads and the current instance to create a new instance. |
||||
* @param quads |
||||
* @returns the dataset instance it was called on. |
||||
*/ |
||||
public addAll( |
||||
quads: Dataset<InAndOutQuad, InAndOutQuad> | InAndOutQuad[], |
||||
): this { |
||||
this.dataset.addAll(quads); |
||||
this.triggerSubscriptionForQuads({ |
||||
added: this.datasetFactory.dataset(quads), |
||||
}); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Bulk add and remove triples |
||||
* @param changed |
||||
*/ |
||||
public bulk(changed: DatasetChanges<InAndOutQuad>): this { |
||||
if (changed.added) { |
||||
this.dataset.addAll(changed.added); |
||||
} |
||||
if (changed.removed) { |
||||
changed.removed.forEach((quad) => { |
||||
this.dataset.delete(quad); |
||||
}); |
||||
} |
||||
this.triggerSubscriptionForQuads(changed); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Returns true if the current instance is a superset of the given dataset; differently put: if the given dataset is a subset of, is contained in the current dataset. |
||||
* Blank Nodes will be normalized. |
||||
* @param other |
||||
*/ |
||||
public contains(other: Dataset<InAndOutQuad, InAndOutQuad>): boolean { |
||||
return this.dataset.contains(other); |
||||
} |
||||
|
||||
/** |
||||
* This method removes the quads in the current instance that match the given arguments. The logic described in Quad Matching is applied for each quad in this dataset to select the quads which will be deleted. |
||||
* @param subject |
||||
* @param predicate |
||||
* @param object |
||||
* @param graph |
||||
* @returns the dataset instance it was called on. |
||||
*/ |
||||
public deleteMatches( |
||||
subject?: Term, |
||||
predicate?: Term, |
||||
object?: Term, |
||||
graph?: Term, |
||||
): this { |
||||
const matching = this.dataset.match(subject, predicate, object, graph); |
||||
for (const quad of matching) { |
||||
this.dataset.delete(quad); |
||||
} |
||||
this.triggerSubscriptionForQuads({ removed: matching }); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Returns a new dataset that contains alls quads from the current dataset, not included in the given dataset. |
||||
* @param other |
||||
*/ |
||||
public difference( |
||||
other: Dataset<InAndOutQuad, InAndOutQuad>, |
||||
): Dataset<InAndOutQuad, InAndOutQuad> { |
||||
return this.dataset.difference(other); |
||||
} |
||||
|
||||
/** |
||||
* Returns true if the current instance contains the same graph structure as the given dataset. |
||||
* @param other |
||||
*/ |
||||
public equals(other: Dataset<InAndOutQuad, InAndOutQuad>): boolean { |
||||
return this.dataset.equals(other); |
||||
} |
||||
|
||||
/** |
||||
* Universal quantification method, tests whether every quad in the dataset passes the test implemented by the provided iteratee. |
||||
* This method immediately returns boolean false once a quad that does not pass the test is found. |
||||
* This method always returns boolean true on an empty dataset. |
||||
* Note: This method is aligned with Array.prototype.every() in ECMAScript-262. |
||||
* @param iteratee |
||||
*/ |
||||
public every( |
||||
iteratee: (quad: InAndOutQuad, dataset: this) => boolean, |
||||
): boolean { |
||||
return this.dataset.every((quad) => iteratee(quad, this)); |
||||
} |
||||
|
||||
/** |
||||
* Creates a new dataset with all the quads that pass the test implemented by the provided iteratee. |
||||
* Note: This method is aligned with Array.prototype.filter() in ECMAScript-262. |
||||
* @param iteratee |
||||
*/ |
||||
public filter( |
||||
iteratee: (quad: InAndOutQuad, dataset: this) => boolean, |
||||
): Dataset<InAndOutQuad, InAndOutQuad> { |
||||
return this.dataset.filter((quad) => iteratee(quad, this)); |
||||
} |
||||
|
||||
/** |
||||
* Executes the provided iteratee once on each quad in the dataset. |
||||
* Note: This method is aligned with Array.prototype.forEach() in ECMAScript-262. |
||||
* @param iteratee |
||||
*/ |
||||
public forEach(iteratee: (quad: InAndOutQuad, dataset: this) => void): void { |
||||
return this.dataset.forEach((quad) => iteratee(quad, this)); |
||||
} |
||||
|
||||
/** |
||||
* Imports all quads from the given stream into the dataset. |
||||
* The stream events end and error are wrapped in a Promise. |
||||
* @param stream |
||||
*/ |
||||
public async import(stream: Stream<InAndOutQuad>): Promise<this> { |
||||
await this.dataset.import(stream); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Returns a new dataset containing alls quads from the current dataset that are also included in the given dataset. |
||||
* @param other |
||||
*/ |
||||
// Typescript disabled because rdf-js has incorrect typings
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
public intersection( |
||||
other: Dataset<InAndOutQuad, InAndOutQuad>, |
||||
): Dataset<InAndOutQuad, InAndOutQuad> { |
||||
return this.dataset.intersection(other); |
||||
} |
||||
|
||||
/** |
||||
* Returns a new dataset containing all quads returned by applying iteratee to each quad in the current dataset. |
||||
* @param iteratee |
||||
*/ |
||||
public map( |
||||
iteratee: (quad: InAndOutQuad, dataset: this) => InAndOutQuad, |
||||
): Dataset<InAndOutQuad, InAndOutQuad> { |
||||
return this.dataset.map((quad) => iteratee(quad, this)); |
||||
} |
||||
|
||||
/** |
||||
* This method calls the iteratee on each quad of the DatasetCore. The first time the iteratee is called, the accumulator value is the initialValue or, if not given, equals to the first quad of the Dataset. The return value of the iteratee is used as accumulator value for the next calls. |
||||
* This method returns the return value of the last iteratee call. |
||||
* Note: This method is aligned with Array.prototype.reduce() in ECMAScript-262. |
||||
* @param iteratee |
||||
* @param initialValue |
||||
*/ |
||||
public reduce<A = unknown>( |
||||
iteratee: (accumulator: A, quad: InAndOutQuad, dataset: this) => A, |
||||
initialValue?: A, |
||||
): A { |
||||
return this.dataset.reduce( |
||||
(acc, quad) => iteratee(acc, quad, this), |
||||
initialValue, |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Existential quantification method, tests whether some quads in the dataset pass the test implemented by the provided iteratee. |
||||
* Note: This method is aligned with Array.prototype.some() in ECMAScript-262. |
||||
* @param iteratee |
||||
* @returns boolean true once a quad that passes the test is found. |
||||
*/ |
||||
public some( |
||||
iteratee: (quad: InAndOutQuad, dataset: this) => boolean, |
||||
): boolean { |
||||
return this.dataset.some((quad) => iteratee(quad, this)); |
||||
} |
||||
|
||||
/** |
||||
* Returns the set of quads within the dataset as a host language native sequence, for example an Array in ECMAScript-262. |
||||
* Note: Since a DatasetCore is an unordered set, the order of the quads within the returned sequence is arbitrary. |
||||
*/ |
||||
public toArray(): InAndOutQuad[] { |
||||
return this.dataset.toArray(); |
||||
} |
||||
|
||||
/** |
||||
* Returns an N-Quads string representation of the dataset, preprocessed with RDF Dataset Normalization algorithm. |
||||
*/ |
||||
public toCanonical(): string { |
||||
return this.dataset.toCanonical(); |
||||
} |
||||
|
||||
/** |
||||
* Returns a stream that contains all quads of the dataset. |
||||
*/ |
||||
public toStream(): Stream<InAndOutQuad> { |
||||
return this.dataset.toStream(); |
||||
} |
||||
|
||||
/** |
||||
* Returns an N-Quads string representation of the dataset. |
||||
* No prior normalization is required, therefore the results for the same quads may vary depending on the Dataset implementation. |
||||
*/ |
||||
public toString(): string { |
||||
return this.dataset.toString(); |
||||
} |
||||
|
||||
/** |
||||
* Returns a new Dataset that is a concatenation of this dataset and the quads given as an argument. |
||||
* @param other |
||||
*/ |
||||
public union( |
||||
quads: Dataset<InAndOutQuad, InAndOutQuad>, |
||||
): Dataset<InAndOutQuad, InAndOutQuad> { |
||||
return this.dataset.union(quads); |
||||
} |
||||
|
||||
/** |
||||
* This method returns a new dataset that is comprised of all quads in the current instance matching the given arguments. The logic described in Quad Matching is applied for each quad in this dataset to check if it should be included in the output dataset. |
||||
* @param subject |
||||
* @param predicate |
||||
* @param object |
||||
* @param graph |
||||
* @returns a Dataset with matching triples |
||||
*/ |
||||
public match( |
||||
subject?: Term | null, |
||||
predicate?: Term | null, |
||||
object?: Term | null, |
||||
graph?: Term | null, |
||||
): Dataset<InAndOutQuad, InAndOutQuad> { |
||||
return this.dataset.match(subject, predicate, object, graph); |
||||
} |
||||
|
||||
/** |
||||
* A non-negative integer that specifies the number of quads in the set. |
||||
*/ |
||||
public get size(): number { |
||||
return this.dataset.size; |
||||
} |
||||
|
||||
/** |
||||
* Adds the specified quad to the dataset. |
||||
* Existing quads, as defined in Quad.equals, will be ignored. |
||||
* @param quad |
||||
* @returns the dataset instance it was called on. |
||||
*/ |
||||
public add(quad: InAndOutQuad): this { |
||||
this.dataset.add(quad); |
||||
this.triggerSubscriptionForQuads({ |
||||
added: this.datasetFactory.dataset([quad]), |
||||
}); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Removes the specified quad from the dataset. |
||||
* This method returns the dataset instance it was called on. |
||||
* @param quad |
||||
*/ |
||||
public delete(quad: InAndOutQuad): this { |
||||
this.dataset.delete(quad); |
||||
this.triggerSubscriptionForQuads({ |
||||
removed: this.datasetFactory.dataset([quad]), |
||||
}); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Determines whether a dataset includes a certain quad, returning true or false as appropriate. |
||||
* @param quad |
||||
*/ |
||||
public has(quad: InAndOutQuad): boolean { |
||||
return this.dataset.has(quad); |
||||
} |
||||
|
||||
/** |
||||
* Returns an iterator |
||||
*/ |
||||
public [Symbol.iterator](): Iterator<InAndOutQuad, unknown, undefined> { |
||||
return this.dataset[Symbol.iterator](); |
||||
} |
||||
|
||||
/** |
||||
* ================================================================== |
||||
* EVENTEMITTER METHODS |
||||
* ================================================================== |
||||
*/ |
||||
private NAMED_NODE_KEY_PREFIX = "NamedNode"; |
||||
private BLANK_NODE_KEY_PREFIX = "BlankNode"; |
||||
private DEFAULT_GRAPH_KEY_PREFIX = "DefaultGraph"; |
||||
private SUBSCRIBABLE_TERMS = [ |
||||
this.NAMED_NODE_KEY_PREFIX, |
||||
this.BLANK_NODE_KEY_PREFIX, |
||||
this.DEFAULT_GRAPH_KEY_PREFIX, |
||||
]; |
||||
|
||||
/** |
||||
* Given a term, returns the string key to be used in an event emitter |
||||
*/ |
||||
private getKeyFromNode(term: SubscribableTerms): string { |
||||
if (term.termType === "NamedNode") { |
||||
return `${this.NAMED_NODE_KEY_PREFIX}${term.value}`; |
||||
} else if (term.termType === "BlankNode") { |
||||
return `${this.BLANK_NODE_KEY_PREFIX}${term.value}`; |
||||
} else if (term.termType === "DefaultGraph") { |
||||
return `${this.DEFAULT_GRAPH_KEY_PREFIX}${term.value}`; |
||||
} |
||||
throw new Error("Invalid term type for subscription"); |
||||
} |
||||
|
||||
/** |
||||
* Given a key, returns the node |
||||
*/ |
||||
private getNodeFromKey(key: string): SubscribableTerms { |
||||
if (key.startsWith(this.NAMED_NODE_KEY_PREFIX)) { |
||||
return namedNode(key.slice(this.NAMED_NODE_KEY_PREFIX.length)); |
||||
} else if (key.startsWith(this.BLANK_NODE_KEY_PREFIX)) { |
||||
return blankNode(key.slice(this.BLANK_NODE_KEY_PREFIX.length)); |
||||
} else if (key.startsWith(this.DEFAULT_GRAPH_KEY_PREFIX)) { |
||||
return defaultGraph(); |
||||
} |
||||
throw Error("Invalid Subscription Key"); |
||||
} |
||||
|
||||
/** |
||||
* Triggers all subscriptions based on an updated quads |
||||
* @param changed The changed triples of the transaction |
||||
*/ |
||||
private triggerSubscriptionForQuads( |
||||
changed: DatasetChanges<InAndOutQuad>, |
||||
): void { |
||||
const triggeredTermsMap: Record<string, SubscribableTerms> = {}; |
||||
const forEachQuad = (quad: BaseQuad) => { |
||||
const subject = quad.subject; |
||||
const predicate = quad.predicate; |
||||
const object = quad.object; |
||||
const graph = quad.graph; |
||||
const quadTerms = [subject, predicate, object, graph]; |
||||
quadTerms.forEach((quadTerm) => { |
||||
if (this.SUBSCRIBABLE_TERMS.includes(quadTerm.termType)) { |
||||
triggeredTermsMap[`${quadTerm.termType}${quadTerm.value}`] = |
||||
quadTerm as SubscribableTerms; |
||||
} |
||||
}); |
||||
}; |
||||
changed.added?.forEach(forEachQuad); |
||||
changed.removed?.forEach(forEachQuad); |
||||
const triggeredTerms = Object.values(triggeredTermsMap); |
||||
triggeredTerms.forEach((triggeredTerm) => { |
||||
this.triggerSubscriptionForNode(triggeredTerm, changed); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Triggers all subscriptions for a given term |
||||
* @param term The term that should be triggered |
||||
* @param changed The changed triples of a certain transaction |
||||
*/ |
||||
private triggerSubscriptionForNode( |
||||
term: SubscribableTerms, |
||||
changed: DatasetChanges<InAndOutQuad>, |
||||
): void { |
||||
if (this.listenerCount(term) > 0) { |
||||
let allQuads: Dataset<InAndOutQuad, InAndOutQuad> = |
||||
this.datasetFactory.dataset(); |
||||
if (term.termType !== "DefaultGraph") { |
||||
allQuads = allQuads.union(this.match(term, null, null, null)); |
||||
allQuads = allQuads.union(this.match(null, null, term, null)); |
||||
if (term.termType !== "BlankNode") { |
||||
allQuads = allQuads.union(this.match(null, term, null, null)); |
||||
allQuads = allQuads.union(this.match(null, null, null, term)); |
||||
} |
||||
} else { |
||||
allQuads = allQuads.union(this.match(null, null, null, term)); |
||||
} |
||||
let changedForThisNode: DatasetChanges<InAndOutQuad> = { |
||||
added: changed.added |
||||
? changed.added.filter( |
||||
(addedQuad) => |
||||
addedQuad.subject.equals(term) || |
||||
addedQuad.predicate.equals(term) || |
||||
addedQuad.object.equals(term) || |
||||
addedQuad.graph.equals(term), |
||||
) |
||||
: undefined, |
||||
removed: changed.removed |
||||
? changed.removed.filter( |
||||
(removedQuad) => |
||||
removedQuad.subject.equals(term) || |
||||
removedQuad.predicate.equals(term) || |
||||
removedQuad.object.equals(term) || |
||||
removedQuad.graph.equals(term), |
||||
) |
||||
: undefined, |
||||
}; |
||||
changedForThisNode = { |
||||
added: |
||||
changedForThisNode.added && changedForThisNode.added.size > 0 |
||||
? changedForThisNode.added |
||||
: undefined, |
||||
removed: |
||||
changedForThisNode.removed && changedForThisNode.removed.size > 0 |
||||
? changedForThisNode.removed |
||||
: undefined, |
||||
}; |
||||
this.emit(term, allQuads, changedForThisNode); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Alias for emitter.on(eventName, listener). |
||||
* @param eventName |
||||
* @param listener |
||||
* @returns |
||||
*/ |
||||
public addListener( |
||||
eventName: SubscribableTerms, |
||||
listener: nodeEventListener<InAndOutQuad>, |
||||
): this { |
||||
return this.on(eventName, listener); |
||||
} |
||||
|
||||
/** |
||||
* Synchronously calls each of the listeners registered for the event named eventName, in the order they were registered, passing the supplied arguments to each. |
||||
* @param eventName |
||||
* @param dataset |
||||
* @param datasetChanges |
||||
* @returns true if the event had listeners, false otherwise. |
||||
*/ |
||||
public emit( |
||||
eventName: SubscribableTerms, |
||||
dataset: Dataset<InAndOutQuad, InAndOutQuad>, |
||||
datasetChanges: DatasetChanges<InAndOutQuad>, |
||||
): boolean { |
||||
return this.eventEmitter.emit( |
||||
this.getKeyFromNode(eventName), |
||||
dataset, |
||||
datasetChanges, |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Returns an array listing the events for which the emitter has registered listeners. The values in the array are strings or Symbols. |
||||
*/ |
||||
public eventNames(): SubscribableTerms[] { |
||||
return this.eventEmitter |
||||
.eventNames() |
||||
.map((eventName) => this.getNodeFromKey(eventName as string)); |
||||
} |
||||
|
||||
/** |
||||
* Returns the current max listener value for the EventEmitter which is either set by emitter.setMaxListeners(n) or defaults to events.defaultMaxListeners. |
||||
*/ |
||||
public getMaxListeners(): number { |
||||
return this.eventEmitter.getMaxListeners(); |
||||
} |
||||
|
||||
/** |
||||
* Returns the number of listeners listening to the event named eventName. |
||||
*/ |
||||
public listenerCount(eventName: SubscribableTerms): number { |
||||
return this.eventEmitter.listenerCount(this.getKeyFromNode(eventName)); |
||||
} |
||||
|
||||
/** |
||||
* Returns a copy of the array of listeners for the event named eventName. |
||||
*/ |
||||
public listeners( |
||||
eventName: SubscribableTerms, |
||||
): nodeEventListener<InAndOutQuad>[] { |
||||
return this.eventEmitter.listeners( |
||||
this.getKeyFromNode(eventName), |
||||
) as nodeEventListener<InAndOutQuad>[]; |
||||
} |
||||
|
||||
/** |
||||
* Alias for emitter.removeListener() |
||||
*/ |
||||
public off( |
||||
eventName: SubscribableTerms, |
||||
listener: nodeEventListener<InAndOutQuad>, |
||||
): void { |
||||
this.removeListener(eventName, listener); |
||||
} |
||||
|
||||
/** |
||||
* Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. |
||||
*/ |
||||
public on( |
||||
eventName: SubscribableTerms, |
||||
listener: nodeEventListener<InAndOutQuad>, |
||||
): this { |
||||
this.eventEmitter.on(this.getKeyFromNode(eventName), listener); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Adds a one-time listener function for the event named eventName. The next time eventName is triggered, this listener is removed and then invoked. |
||||
*/ |
||||
public once( |
||||
eventName: SubscribableTerms, |
||||
listener: nodeEventListener<InAndOutQuad>, |
||||
): this { |
||||
this.eventEmitter.once(this.getKeyFromNode(eventName), listener); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Adds the listener function to the beginning of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. |
||||
*/ |
||||
public prependListener( |
||||
eventName: SubscribableTerms, |
||||
listener: nodeEventListener<InAndOutQuad>, |
||||
): this { |
||||
this.eventEmitter.prependListener(this.getKeyFromNode(eventName), listener); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Adds a one-time listener function for the event named eventName to the beginning of the listeners array. The next time eventName is triggered, this listener is removed, and then invoked. |
||||
*/ |
||||
public prependOnceListener( |
||||
eventName: SubscribableTerms, |
||||
listener: nodeEventListener<InAndOutQuad>, |
||||
): this { |
||||
this.eventEmitter.prependOnceListener( |
||||
this.getKeyFromNode(eventName), |
||||
listener, |
||||
); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Removes all listeners, or those of the specified eventName. |
||||
*/ |
||||
public removeAllListeners(eventName: SubscribableTerms): this { |
||||
this.eventEmitter.removeAllListeners(this.getKeyFromNode(eventName)); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Removes the specified listener from the listener array for the event named eventName. |
||||
*/ |
||||
public removeListener( |
||||
eventName: SubscribableTerms, |
||||
listener: nodeEventListener<InAndOutQuad>, |
||||
): this { |
||||
this.eventEmitter.removeListener(this.getKeyFromNode(eventName), listener); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* By default EventEmitters will print a warning if more than 10 listeners are added for a particular event. This is a useful default that helps finding memory leaks. The emitter.setMaxListeners() method allows the limit to be modified for this specific EventEmitter instance. The value can be set to Infinity (or 0) to indicate an unlimited number of listeners. |
||||
*/ |
||||
public setMaxListeners(n: number): this { |
||||
this.eventEmitter.setMaxListeners(n); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Returns a copy of the array of listeners for the event named eventName, including any wrappers (such as those created by .once()). |
||||
*/ |
||||
public rawListeners( |
||||
eventName: SubscribableTerms, |
||||
): nodeEventListener<InAndOutQuad>[] { |
||||
return this.eventEmitter.rawListeners( |
||||
this.getKeyFromNode(eventName), |
||||
) as nodeEventListener[]; |
||||
} |
||||
|
||||
/** |
||||
* ================================================================== |
||||
* TRANSACTION METHODS |
||||
* ================================================================== |
||||
*/ |
||||
|
||||
/** |
||||
* Returns a transactional dataset that will update this dataset when its transaction is committed. |
||||
*/ |
||||
public startTransaction(): TransactionalDataset<InAndOutQuad> { |
||||
// Type problem again
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
return new ProxyTransactionalDataset(this, this.datasetFactory); |
||||
} |
||||
} |
@ -0,0 +1,27 @@ |
||||
import type { DatasetFactory, BaseQuad, Dataset } from "@rdfjs/types"; |
||||
import WrapperSubscribableDataset from "./WrapperSubscribableDataset"; |
||||
|
||||
/** |
||||
* A DatasetFactory that returns a WrapperSubscribableDataset given a generic DatasetFactory. |
||||
*/ |
||||
export default class WrapperSubscribableDatasetFactory< |
||||
InAndOutQuad extends BaseQuad = BaseQuad, |
||||
> implements DatasetFactory<InAndOutQuad, InAndOutQuad> |
||||
{ |
||||
private datasetFactory: DatasetFactory<InAndOutQuad, InAndOutQuad>; |
||||
constructor(datasetFactory: DatasetFactory<InAndOutQuad, InAndOutQuad>) { |
||||
this.datasetFactory = datasetFactory; |
||||
} |
||||
|
||||
dataset( |
||||
quads?: Dataset<InAndOutQuad, InAndOutQuad> | InAndOutQuad[], |
||||
): WrapperSubscribableDataset<InAndOutQuad> { |
||||
// Typings are wrong
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
return new WrapperSubscribableDataset( |
||||
this.datasetFactory, |
||||
quads ? this.datasetFactory.dataset(quads) : undefined, |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,30 @@ |
||||
import type { Dataset, DatasetFactory, Quad } from "@rdfjs/types"; |
||||
import type { WrapperSubscribableDataset } from "."; |
||||
import { createDataset } from "@ldo/dataset"; |
||||
import WrapperSubscribableDatasetFactory from "./WrapperSubscribableDatasetFactory"; |
||||
|
||||
/** |
||||
* Creates a dataset factory that generates a SubscribableDataset |
||||
* @returns DatasetFactory for SubscribableDataset |
||||
*/ |
||||
export function createWrapperSubscribableDatasetFactory(): WrapperSubscribableDatasetFactory<Quad> { |
||||
const datasetFactory: DatasetFactory<Quad> = { |
||||
dataset: (quads?: Dataset<Quad> | Quad[]): Dataset<Quad> => { |
||||
return createDataset(quads); |
||||
}, |
||||
}; |
||||
return new WrapperSubscribableDatasetFactory(datasetFactory); |
||||
} |
||||
|
||||
/** |
||||
* Creates a SubscribableDataset |
||||
* @param quads: A dataset or array of Quads to initialize the dataset. |
||||
* @returns Dataset |
||||
*/ |
||||
export default function createWrapperSubscribableDataset( |
||||
quads?: Dataset<Quad> | Quad[], |
||||
): WrapperSubscribableDataset<Quad> { |
||||
const wrapperSubscribableDatasetFactory = |
||||
createWrapperSubscribableDatasetFactory(); |
||||
return wrapperSubscribableDatasetFactory.dataset(quads); |
||||
} |
@ -0,0 +1,28 @@ |
||||
import type { ParserOptions } from "n3"; |
||||
import type { Quad } from "@rdfjs/types"; |
||||
import { createDatasetFromSerializedInput } from "@ldo/dataset"; |
||||
import { createWrapperSubscribableDatasetFactory } from "./createWrapperSubscribableDataset"; |
||||
import type WrapperSubscribableDataset from "./WrapperSubscribableDataset"; |
||||
|
||||
/** |
||||
* Creates a SubscribableDataset with a string input that could be JSON-LD, Turtle, N-Triples, TriG, RDF*, or N3. |
||||
* @param data A string representation of RDF Data in JSON-LD, Turtle, N-Triples, TriG, RDF*, or N3. |
||||
* @param options Parser options: { |
||||
* format?: string; |
||||
* factory?: RDF.DataFactory; |
||||
* baseIRI?: string; |
||||
* blankNodePrefix?: string; |
||||
* } |
||||
* @returns A dataset |
||||
*/ |
||||
export default async function createWrapperSubscribableDatasetDataserFromSerializedInput( |
||||
data: string, |
||||
options?: ParserOptions, |
||||
): Promise<WrapperSubscribableDataset<Quad>> { |
||||
const datasetFactory = createWrapperSubscribableDatasetFactory(); |
||||
return createDatasetFromSerializedInput<WrapperSubscribableDataset<Quad>>( |
||||
datasetFactory, |
||||
data, |
||||
options, |
||||
); |
||||
} |
@ -0,0 +1,9 @@ |
||||
export { |
||||
default as createSubscribableDataset, |
||||
createWrapperSubscribableDatasetFactory as createSubscribableDatasetFactory, |
||||
} from "./createWrapperSubscribableDataset"; |
||||
export { default as serializedToSubscribableDataset } from "./createWrapperSubscribableDatasetFromSerializedInput"; |
||||
export { default as ProxyTransactionalDataset } from "./ProxyTransactionalDataset"; |
||||
export { default as WrapperSubscribableDataset } from "./WrapperSubscribableDataset"; |
||||
export { default as WrapperSubscribableDatasetFactory } from "./WrapperSubscribableDatasetFactory"; |
||||
export * from "./types"; |
@ -0,0 +1,171 @@ |
||||
import type { |
||||
Dataset, |
||||
NamedNode, |
||||
BlankNode, |
||||
DefaultGraph, |
||||
BaseQuad, |
||||
} from "@rdfjs/types"; |
||||
/** |
||||
* An interface representing the changes made |
||||
*/ |
||||
export interface DatasetChanges<InAndOutQuad extends BaseQuad = BaseQuad> { |
||||
added?: Dataset<InAndOutQuad, InAndOutQuad>; |
||||
removed?: Dataset<InAndOutQuad, InAndOutQuad>; |
||||
} |
||||
|
||||
/** |
||||
* Types of nodes a subscribable dataset can subscribe to |
||||
*/ |
||||
export type SubscribableTerms = NamedNode | BlankNode | DefaultGraph; |
||||
|
||||
/** |
||||
* An event listeners for nodes |
||||
*/ |
||||
export type nodeEventListener<InAndOutQuad extends BaseQuad = BaseQuad> = ( |
||||
dataset: Dataset<InAndOutQuad, InAndOutQuad>, |
||||
changes: DatasetChanges<InAndOutQuad>, |
||||
) => void; |
||||
|
||||
/** |
||||
* Adds the bulk method for add and remove |
||||
*/ |
||||
export interface BulkEditableDataset<InAndOutQuad extends BaseQuad = BaseQuad> |
||||
extends Dataset<InAndOutQuad, InAndOutQuad> { |
||||
bulk(changes: DatasetChanges<InAndOutQuad>): this; |
||||
} |
||||
|
||||
/** |
||||
* A dataset that allows you to modify the dataset and |
||||
*/ |
||||
export interface TransactionalDataset<InAndOutQuad extends BaseQuad = BaseQuad> |
||||
extends Dataset<InAndOutQuad, InAndOutQuad> { |
||||
rollback(): void; |
||||
commit(): void; |
||||
getChanges(): DatasetChanges<InAndOutQuad>; |
||||
} |
||||
|
||||
/** |
||||
* Dataset that allows developers to subscribe to a sepecific term and be alerted |
||||
* if a quad is added or removed containing that term. It's methods follow the |
||||
* EventEmitter interface except take in namedNodes as keys. |
||||
*/ |
||||
export interface SubscribableDataset<InAndOutQuad extends BaseQuad = BaseQuad> |
||||
extends BulkEditableDataset<InAndOutQuad> { |
||||
/** |
||||
* Alias for emitter.on(eventName, listener). |
||||
* @param eventName |
||||
* @param listener |
||||
* @returns |
||||
*/ |
||||
addListener( |
||||
eventName: SubscribableTerms, |
||||
listener: nodeEventListener<InAndOutQuad>, |
||||
): this; |
||||
|
||||
/** |
||||
* Synchronously calls each of the listeners registered for the event named eventName, in the order they were registered, passing the supplied arguments to each. |
||||
* @param eventName |
||||
* @param dataset |
||||
* @param datasetChanges |
||||
* @returns true if the event had listeners, false otherwise. |
||||
*/ |
||||
emit( |
||||
eventName: SubscribableTerms, |
||||
dataset: Dataset<InAndOutQuad, InAndOutQuad>, |
||||
datasetChanges: DatasetChanges<InAndOutQuad>, |
||||
): boolean; |
||||
|
||||
/** |
||||
* Returns an array listing the events for which the emitter has registered listeners. The values in the array are strings or Symbols. |
||||
*/ |
||||
eventNames(): SubscribableTerms[]; |
||||
|
||||
/** |
||||
* Returns the current max listener value for the EventEmitter which is either set by emitter.setMaxListeners(n) or defaults to events.defaultMaxListeners. |
||||
*/ |
||||
getMaxListeners(): number; |
||||
|
||||
/** |
||||
* Returns the number of listeners listening to the event named eventName. |
||||
*/ |
||||
listenerCount(eventName: SubscribableTerms): number; |
||||
|
||||
/** |
||||
* Returns a copy of the array of listeners for the event named eventName. |
||||
*/ |
||||
listeners(eventName: SubscribableTerms): nodeEventListener<InAndOutQuad>[]; |
||||
|
||||
/** |
||||
* Alias for emitter.removeListener() |
||||
*/ |
||||
off( |
||||
eventName: SubscribableTerms, |
||||
listener: nodeEventListener<InAndOutQuad>, |
||||
): void; |
||||
|
||||
/** |
||||
* Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. |
||||
*/ |
||||
on( |
||||
eventName: SubscribableTerms, |
||||
listener: nodeEventListener<InAndOutQuad>, |
||||
): this; |
||||
|
||||
/** |
||||
* Adds a one-time listener function for the event named eventName. The next time eventName is triggered, this listener is removed and then invoked. |
||||
*/ |
||||
once( |
||||
eventName: SubscribableTerms, |
||||
listener: nodeEventListener<InAndOutQuad>, |
||||
): this; |
||||
|
||||
/** |
||||
* Adds the listener function to the beginning of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. |
||||
*/ |
||||
prependListener( |
||||
eventName: SubscribableTerms, |
||||
listener: nodeEventListener<InAndOutQuad>, |
||||
): this; |
||||
|
||||
/** |
||||
* Adds a one-time listener function for the event named eventName to the beginning of the listeners array. The next time eventName is triggered, this listener is removed, and then invoked. |
||||
*/ |
||||
prependOnceListener( |
||||
eventName: SubscribableTerms, |
||||
listener: nodeEventListener<InAndOutQuad>, |
||||
): this; |
||||
|
||||
/** |
||||
* Removes all listeners, or those of the specified eventName. |
||||
*/ |
||||
removeAllListeners(eventName: SubscribableTerms): this; |
||||
|
||||
/** |
||||
* Removes the specified listener from the listener array for the event named eventName. |
||||
*/ |
||||
removeListener( |
||||
eventName: SubscribableTerms, |
||||
listener: nodeEventListener<InAndOutQuad>, |
||||
): this; |
||||
|
||||
/** |
||||
* By default EventEmitters will print a warning if more than 10 listeners are added for a particular event. This is a useful default that helps finding memory leaks. The emitter.setMaxListeners() method allows the limit to be modified for this specific EventEmitter instance. The value can be set to Infinity (or 0) to indicate an unlimited number of listeners. |
||||
*/ |
||||
setMaxListeners(n: number): this; |
||||
|
||||
/** |
||||
* Returns a copy of the array of listeners for the event named eventName, including any wrappers (such as those created by .once()). |
||||
*/ |
||||
rawListeners(eventName: SubscribableTerms): nodeEventListener<InAndOutQuad>[]; |
||||
|
||||
/** |
||||
* ================================================================== |
||||
* TRANSACTION METHODS |
||||
* ================================================================== |
||||
*/ |
||||
|
||||
/** |
||||
* Returns a transactional dataset that will update this dataset when its transaction is committed. |
||||
*/ |
||||
startTransaction(): TransactionalDataset<InAndOutQuad>; |
||||
} |
@ -0,0 +1,327 @@ |
||||
import type { |
||||
Dataset, |
||||
DatasetCoreFactory, |
||||
Quad, |
||||
DatasetCore, |
||||
} from "@rdfjs/types"; |
||||
import type { BulkEditableDataset } from "../src"; |
||||
import { ExtendedDatasetFactory } from "@ldo/dataset"; |
||||
import { ProxyTransactionalDataset } from "../src"; |
||||
import { namedNode, literal, quad } from "@rdfjs/data-model"; |
||||
import datasetCoreFactory from "@rdfjs/dataset"; |
||||
|
||||
describe("ProxyTransactionalDataset", () => { |
||||
let parentDataset: Dataset<Quad>; |
||||
let transactionalDataset: ProxyTransactionalDataset<Quad>; |
||||
const tomTypeQuad = quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
); |
||||
const tomNameQuad = quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Tom"), |
||||
); |
||||
const lickyNameQuad = quad( |
||||
namedNode("http://example.org/cartoons#Licky"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Licky"), |
||||
); |
||||
const lickyTypeQuad = quad( |
||||
namedNode("http://example.org/cartoons#Licky"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
); |
||||
const datasetFactory: DatasetCoreFactory = { |
||||
dataset: (quads?: Dataset | Quad[]): DatasetCore => { |
||||
return datasetCoreFactory.dataset(quads ? Array.from(quads) : undefined); |
||||
}, |
||||
}; |
||||
const extendedDatasetFactory = new ExtendedDatasetFactory(datasetFactory); |
||||
|
||||
const initializeWithExtendedDatasetParent = (quads?: Quad[]) => { |
||||
parentDataset = extendedDatasetFactory.dataset( |
||||
quads || [tomTypeQuad, tomNameQuad], |
||||
); |
||||
transactionalDataset = new ProxyTransactionalDataset( |
||||
parentDataset, |
||||
extendedDatasetFactory, |
||||
); |
||||
}; |
||||
|
||||
beforeEach(() => { |
||||
initializeWithExtendedDatasetParent(); |
||||
}); |
||||
|
||||
it("Adds without adding to the parent", () => { |
||||
const addedQuad = lickyNameQuad; |
||||
transactionalDataset.add(addedQuad); |
||||
expect(transactionalDataset.has(addedQuad)).toBe(true); |
||||
expect(parentDataset.has(addedQuad)).toBe(false); |
||||
}); |
||||
|
||||
it("Deletes without deleting from the parent", () => { |
||||
const deletedQuad = tomTypeQuad; |
||||
transactionalDataset.delete(deletedQuad); |
||||
expect(transactionalDataset.has(deletedQuad)).toBe(false); |
||||
expect(parentDataset.has(deletedQuad)).toBe(true); |
||||
}); |
||||
|
||||
it("Adds a quad already in the dataset with no duplicates", () => { |
||||
const addedQuad = tomTypeQuad; |
||||
transactionalDataset.add(addedQuad); |
||||
const arr = transactionalDataset.toArray(); |
||||
expect(arr.length).toBe(2); |
||||
expect(arr.some((curQuad) => curQuad.equals(tomNameQuad))).toBe(true); |
||||
expect(arr.some((curQuad) => curQuad.equals(tomTypeQuad))).toBe(true); |
||||
}); |
||||
|
||||
it("Adds multiple quads", () => { |
||||
transactionalDataset.add(lickyNameQuad); |
||||
transactionalDataset.add(lickyTypeQuad); |
||||
expect(transactionalDataset.size).toBe(4); |
||||
expect(transactionalDataset.has(lickyNameQuad)).toBe(true); |
||||
expect(transactionalDataset.has(lickyTypeQuad)).toBe(true); |
||||
}); |
||||
|
||||
it("Removes a quad that's not in the dataset and nothing happens", () => { |
||||
const removedQuad = lickyNameQuad; |
||||
transactionalDataset.delete(removedQuad); |
||||
const arr = transactionalDataset.toArray(); |
||||
expect(arr.length).toBe(2); |
||||
expect(arr.some((curQuad) => curQuad.equals(tomNameQuad))).toBe(true); |
||||
expect(arr.some((curQuad) => curQuad.equals(tomTypeQuad))).toBe(true); |
||||
}); |
||||
|
||||
it("Removes multiple quads", () => { |
||||
transactionalDataset.delete(tomNameQuad); |
||||
transactionalDataset.delete(tomTypeQuad); |
||||
expect(transactionalDataset.size).toBe(0); |
||||
}); |
||||
|
||||
it("Removes then adds a quad and they cancel out", () => { |
||||
const removedQuad = tomTypeQuad; |
||||
transactionalDataset.delete(removedQuad); |
||||
transactionalDataset.add(removedQuad); |
||||
const arr = transactionalDataset.toArray(); |
||||
expect(arr.length).toBe(2); |
||||
expect(arr.some((curQuad) => curQuad.equals(tomNameQuad))).toBe(true); |
||||
expect(arr.some((curQuad) => curQuad.equals(tomTypeQuad))).toBe(true); |
||||
}); |
||||
|
||||
it("Adds then removes a quad and they cancel out", () => { |
||||
const addedQuad = lickyNameQuad; |
||||
transactionalDataset.add(addedQuad); |
||||
transactionalDataset.delete(addedQuad); |
||||
const arr = transactionalDataset.toArray(); |
||||
expect(arr.length).toBe(2); |
||||
expect(arr.some((curQuad) => curQuad.equals(tomNameQuad))).toBe(true); |
||||
expect(arr.some((curQuad) => curQuad.equals(tomTypeQuad))).toBe(true); |
||||
}); |
||||
|
||||
it("Commits added changes", () => { |
||||
transactionalDataset.add(lickyNameQuad); |
||||
transactionalDataset.commit(); |
||||
expect(parentDataset.has(lickyNameQuad)).toBe(true); |
||||
}); |
||||
|
||||
it("Commits removed changes", () => { |
||||
transactionalDataset.delete(tomTypeQuad); |
||||
transactionalDataset.commit(); |
||||
expect(parentDataset.has(tomTypeQuad)).toBe(false); |
||||
}); |
||||
|
||||
it("Commits added and removed changes", () => { |
||||
transactionalDataset.add(lickyNameQuad); |
||||
transactionalDataset.delete(tomTypeQuad); |
||||
transactionalDataset.commit(); |
||||
expect(parentDataset.has(lickyNameQuad)).toBe(true); |
||||
expect(parentDataset.has(tomTypeQuad)).toBe(false); |
||||
}); |
||||
|
||||
it("Errors whenever you try to add or remove triples after committing.", () => { |
||||
transactionalDataset.commit(); |
||||
expect(() => transactionalDataset.add(lickyNameQuad)).toThrow( |
||||
"Transaction has already committed", |
||||
); |
||||
expect(() => transactionalDataset.delete(tomTypeQuad)).toThrow( |
||||
"Transaction has already committed", |
||||
); |
||||
expect(() => transactionalDataset.addAll([lickyNameQuad])).toThrow( |
||||
"Transaction has already committed", |
||||
); |
||||
expect(() => |
||||
transactionalDataset.deleteMatches( |
||||
tomTypeQuad.subject, |
||||
tomTypeQuad.predicate, |
||||
tomTypeQuad.object, |
||||
), |
||||
).toThrow("Transaction has already committed"); |
||||
// Nothing Matches it should still error
|
||||
expect(() => |
||||
transactionalDataset.deleteMatches( |
||||
lickyNameQuad.subject, |
||||
lickyNameQuad.predicate, |
||||
lickyNameQuad.object, |
||||
), |
||||
).toThrow("Transaction has already committed"); |
||||
}); |
||||
|
||||
it("Does not rollback unless it has already committed", () => { |
||||
expect(() => transactionalDataset.rollback()).toThrow( |
||||
"Cannot rollback. Transaction has not yet been committed", |
||||
); |
||||
}); |
||||
|
||||
it("Rolls back the dataset", () => { |
||||
transactionalDataset.add(lickyNameQuad); |
||||
transactionalDataset.delete(tomTypeQuad); |
||||
transactionalDataset.commit(); |
||||
transactionalDataset.rollback(); |
||||
expect(parentDataset.size).toBe(2); |
||||
expect(parentDataset.has(tomTypeQuad)).toBe(true); |
||||
expect(parentDataset.has(tomNameQuad)).toBe(true); |
||||
}); |
||||
|
||||
it("Rolls back the dataset when redundant changes are made", () => { |
||||
transactionalDataset.add(tomTypeQuad); |
||||
transactionalDataset.delete(lickyNameQuad); |
||||
transactionalDataset.commit(); |
||||
transactionalDataset.rollback(); |
||||
expect(parentDataset.size).toBe(2); |
||||
expect(parentDataset.has(tomTypeQuad)).toBe(true); |
||||
expect(parentDataset.has(tomNameQuad)).toBe(true); |
||||
}); |
||||
|
||||
it("Counts the correct size given added quads", () => { |
||||
transactionalDataset.add(lickyNameQuad); |
||||
expect(transactionalDataset.size).toBe(3); |
||||
}); |
||||
|
||||
it("Counts the correct size given added redundant quads", () => { |
||||
transactionalDataset.add(tomTypeQuad); |
||||
expect(transactionalDataset.size).toBe(2); |
||||
}); |
||||
|
||||
it("Counts the correct size given deleted quads", () => { |
||||
transactionalDataset.delete(tomTypeQuad); |
||||
expect(transactionalDataset.size).toBe(1); |
||||
}); |
||||
|
||||
it("Counts the correct size given deleted redundant quads", () => { |
||||
transactionalDataset.delete(lickyNameQuad); |
||||
expect(transactionalDataset.size).toBe(2); |
||||
}); |
||||
|
||||
it("Counts the correct size given added and deleted quads", () => { |
||||
transactionalDataset.add(lickyNameQuad); |
||||
transactionalDataset.delete(tomTypeQuad); |
||||
expect(transactionalDataset.size).toBe(2); |
||||
}); |
||||
|
||||
it("Adds all", () => { |
||||
transactionalDataset.addAll([lickyNameQuad, lickyTypeQuad]); |
||||
expect(transactionalDataset.has(lickyTypeQuad)).toBe(true); |
||||
expect(transactionalDataset.has(lickyNameQuad)).toBe(true); |
||||
}); |
||||
|
||||
it("Makes changes in bulk", () => { |
||||
transactionalDataset.bulk({ |
||||
added: extendedDatasetFactory.dataset([lickyNameQuad]), |
||||
removed: extendedDatasetFactory.dataset([tomTypeQuad]), |
||||
}); |
||||
expect(transactionalDataset.size).toBe(2); |
||||
expect(transactionalDataset.has(tomTypeQuad)).toBe(false); |
||||
expect(transactionalDataset.has(lickyNameQuad)).toBe(true); |
||||
}); |
||||
|
||||
it("Deletes matching data", () => { |
||||
transactionalDataset.deleteMatches( |
||||
tomTypeQuad.subject, |
||||
undefined, |
||||
undefined, |
||||
); |
||||
expect(transactionalDataset.size).toBe(0); |
||||
}); |
||||
|
||||
it("Matches an unmodified dataset", () => { |
||||
const matchingDataset = transactionalDataset.match( |
||||
tomTypeQuad.subject, |
||||
undefined, |
||||
undefined, |
||||
); |
||||
expect(matchingDataset.size).toBe(2); |
||||
expect(matchingDataset.has(tomTypeQuad)).toBe(true); |
||||
expect(matchingDataset.has(tomNameQuad)).toBe(true); |
||||
}); |
||||
|
||||
it("Matches a dataset with a removed quad", () => { |
||||
initializeWithExtendedDatasetParent([ |
||||
tomNameQuad, |
||||
tomTypeQuad, |
||||
lickyNameQuad, |
||||
lickyTypeQuad, |
||||
]); |
||||
transactionalDataset.delete(tomNameQuad); |
||||
const matchingDataset = transactionalDataset.match( |
||||
tomTypeQuad.subject, |
||||
undefined, |
||||
undefined, |
||||
); |
||||
expect(matchingDataset.size).toBe(1); |
||||
expect(matchingDataset.has(tomTypeQuad)).toBe(true); |
||||
expect(matchingDataset.has(tomNameQuad)).toBe(false); |
||||
}); |
||||
|
||||
it("matches a dataset with an added quad", () => { |
||||
transactionalDataset.add(lickyNameQuad); |
||||
const matchingDataset = transactionalDataset.match( |
||||
undefined, |
||||
tomNameQuad.predicate, |
||||
undefined, |
||||
); |
||||
expect(matchingDataset.size).toBe(2); |
||||
expect(matchingDataset.has(lickyNameQuad)).toBe(true); |
||||
expect(matchingDataset.has(tomNameQuad)).toBe(true); |
||||
}); |
||||
|
||||
it("Uses bulk update on commit when the parent dataset is bulk updatable", () => { |
||||
// Disable for tests
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const mockParent: BulkEditableDataset<Quad> = { |
||||
bulk: jest.fn(), |
||||
has: (curQuad) => parentDataset.has(curQuad), |
||||
[Symbol.iterator]: () => parentDataset[Symbol.iterator](), |
||||
}; |
||||
transactionalDataset = new ProxyTransactionalDataset<Quad>( |
||||
mockParent, |
||||
extendedDatasetFactory, |
||||
); |
||||
|
||||
transactionalDataset.add(lickyNameQuad); |
||||
transactionalDataset.delete(tomTypeQuad); |
||||
transactionalDataset.commit(); |
||||
expect(mockParent.bulk).toHaveBeenCalled(); |
||||
}); |
||||
|
||||
it("Returns a transactional dataset", () => { |
||||
expect( |
||||
transactionalDataset.startTransaction() instanceof |
||||
ProxyTransactionalDataset, |
||||
).toBe(true); |
||||
}); |
||||
|
||||
it("Checks if it has a quad when it wasn't added to the transaction but was in the original dataset", () => { |
||||
expect(transactionalDataset.has(tomTypeQuad)).toBe(true); |
||||
}); |
||||
|
||||
it("returns the dataset changes", () => { |
||||
const addedQuad = lickyNameQuad; |
||||
transactionalDataset.add(addedQuad); |
||||
const datasetChanges = transactionalDataset.getChanges(); |
||||
expect(datasetChanges.added?.size).toBe(1); |
||||
expect(datasetChanges.removed).toBe(undefined); |
||||
}); |
||||
}); |
@ -0,0 +1,428 @@ |
||||
import type { SubscribableDataset } from "../src"; |
||||
import { ProxyTransactionalDataset, createSubscribableDataset } from "../src"; |
||||
import { createDataset } from "@ldo/dataset"; |
||||
import { |
||||
namedNode, |
||||
literal, |
||||
quad, |
||||
defaultGraph, |
||||
blankNode, |
||||
} from "@rdfjs/data-model"; |
||||
import type { Quad, BlankNode } from "@rdfjs/types"; |
||||
import testDataset from "@ldo/dataset/test/dataset.testHelper"; |
||||
|
||||
describe("WrapperSubscribableDataset", () => { |
||||
// Regular dataset tests
|
||||
testDataset({ |
||||
dataset: createSubscribableDataset, |
||||
}); |
||||
|
||||
// Subscribable Dataset tests
|
||||
let subscribableDatastet: SubscribableDataset<Quad>; |
||||
const tomTypeQuad = quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
); |
||||
const tomNameQuad = quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Tom"), |
||||
); |
||||
const tomColorQuad = quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://example.org/cartoons#color"), |
||||
namedNode("http://example.org/colors#grey"), |
||||
); |
||||
const lickyNameQuad = quad( |
||||
namedNode("http://example.org/cartoons#Licky"), |
||||
namedNode("http://example.org/cartoons#name"), |
||||
literal("Licky"), |
||||
); |
||||
const lickyTypeQuad = quad( |
||||
namedNode("http://example.org/cartoons#Licky"), |
||||
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||
namedNode("http://example.org/cartoons#Cat"), |
||||
); |
||||
|
||||
beforeEach(() => { |
||||
subscribableDatastet = createSubscribableDataset([ |
||||
tomTypeQuad, |
||||
tomNameQuad, |
||||
]); |
||||
}); |
||||
|
||||
it("Alerts when a node is added", () => { |
||||
const callbackFunc = jest.fn(); |
||||
subscribableDatastet.addListener( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
callbackFunc, |
||||
); |
||||
subscribableDatastet.add(tomColorQuad); |
||||
expect(callbackFunc).toBeCalledTimes(1); |
||||
expect(callbackFunc.mock.calls[0][0].size).toBe(3); |
||||
expect(callbackFunc.mock.calls[0][0].has(tomNameQuad)).toBe(true); |
||||
expect(callbackFunc.mock.calls[0][0].has(tomTypeQuad)).toBe(true); |
||||
expect(callbackFunc.mock.calls[0][0].has(tomColorQuad)).toBe(true); |
||||
expect(callbackFunc.mock.calls[0][1].added.size).toBe(1); |
||||
expect(callbackFunc.mock.calls[0][1].added.has(tomColorQuad)).toBe(true); |
||||
}); |
||||
|
||||
it("Alerts when a node is removed", () => { |
||||
const callbackFunc = jest.fn(); |
||||
subscribableDatastet.on( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
callbackFunc, |
||||
); |
||||
subscribableDatastet.delete(tomTypeQuad); |
||||
expect(callbackFunc).toBeCalledTimes(1); |
||||
expect(callbackFunc.mock.calls[0][0].size).toBe(1); |
||||
expect(callbackFunc.mock.calls[0][0].has(tomNameQuad)).toBe(true); |
||||
expect(callbackFunc.mock.calls[0][1].removed.size).toBe(1); |
||||
expect(callbackFunc.mock.calls[0][1].removed.has(tomTypeQuad)).toBe(true); |
||||
}); |
||||
|
||||
it("Alerts when multiple quads are added", () => { |
||||
const callbackFunc = jest.fn(); |
||||
subscribableDatastet.on( |
||||
namedNode("http://example.org/cartoons#Licky"), |
||||
callbackFunc, |
||||
); |
||||
subscribableDatastet.addAll([lickyNameQuad, lickyTypeQuad]); |
||||
expect(callbackFunc).toBeCalledTimes(1); |
||||
expect(callbackFunc.mock.calls[0][0].size).toBe(2); |
||||
expect(callbackFunc.mock.calls[0][0].has(lickyTypeQuad)).toBe(true); |
||||
expect(callbackFunc.mock.calls[0][0].has(lickyNameQuad)).toBe(true); |
||||
expect(callbackFunc.mock.calls[0][1].added.size).toBe(2); |
||||
expect(callbackFunc.mock.calls[0][1].added.has(lickyNameQuad)).toBe(true); |
||||
expect(callbackFunc.mock.calls[0][1].added.has(lickyTypeQuad)).toBe(true); |
||||
}); |
||||
|
||||
it("Alerts when bulk updated by only adding", () => { |
||||
const callbackFuncLicky = jest.fn(); |
||||
subscribableDatastet.on( |
||||
namedNode("http://example.org/cartoons#Licky"), |
||||
callbackFuncLicky, |
||||
); |
||||
subscribableDatastet.bulk({ |
||||
added: createDataset([lickyTypeQuad]), |
||||
}); |
||||
expect(callbackFuncLicky).toBeCalledTimes(1); |
||||
expect( |
||||
callbackFuncLicky.mock.calls[0][0].equals(createDataset([lickyTypeQuad])), |
||||
).toBe(true); |
||||
expect( |
||||
callbackFuncLicky.mock.calls[0][1].added.equals( |
||||
createDataset([lickyTypeQuad]), |
||||
), |
||||
).toBe(true); |
||||
expect(callbackFuncLicky.mock.calls[0][1].removed).toBe(undefined); |
||||
}); |
||||
|
||||
it("Alerts when bulk updated by only removing", () => { |
||||
const callbackFuncTom = jest.fn(); |
||||
subscribableDatastet.on( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
callbackFuncTom, |
||||
); |
||||
subscribableDatastet.bulk({ |
||||
removed: createDataset([tomTypeQuad]), |
||||
}); |
||||
expect(callbackFuncTom).toBeCalledTimes(1); |
||||
expect( |
||||
callbackFuncTom.mock.calls[0][0].equals(createDataset([tomNameQuad])), |
||||
).toBe(true); |
||||
expect( |
||||
callbackFuncTom.mock.calls[0][1].removed.equals( |
||||
createDataset([tomTypeQuad]), |
||||
), |
||||
).toBe(true); |
||||
expect(callbackFuncTom.mock.calls[0][1].added).toBe(undefined); |
||||
}); |
||||
|
||||
it("Alerts when bulk updated", () => { |
||||
const callbackFuncLicky = jest.fn(); |
||||
const callbackFuncTom = jest.fn(); |
||||
subscribableDatastet.on( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
callbackFuncTom, |
||||
); |
||||
subscribableDatastet.on( |
||||
namedNode("http://example.org/cartoons#Licky"), |
||||
callbackFuncLicky, |
||||
); |
||||
subscribableDatastet.bulk({ |
||||
added: createDataset([lickyTypeQuad]), |
||||
removed: createDataset([tomTypeQuad]), |
||||
}); |
||||
expect(callbackFuncLicky).toBeCalledTimes(1); |
||||
expect(callbackFuncTom).toBeCalledTimes(1); |
||||
expect( |
||||
callbackFuncLicky.mock.calls[0][0].equals(createDataset([lickyTypeQuad])), |
||||
).toBe(true); |
||||
expect( |
||||
callbackFuncTom.mock.calls[0][0].equals(createDataset([tomNameQuad])), |
||||
).toBe(true); |
||||
expect( |
||||
callbackFuncLicky.mock.calls[0][1].added.equals( |
||||
createDataset([lickyTypeQuad]), |
||||
), |
||||
).toBe(true); |
||||
expect(callbackFuncLicky.mock.calls[0][1].removed).toBe(undefined); |
||||
expect( |
||||
callbackFuncTom.mock.calls[0][1].removed.equals( |
||||
createDataset([tomTypeQuad]), |
||||
), |
||||
).toBe(true); |
||||
expect(callbackFuncTom.mock.calls[0][1].added).toBe(undefined); |
||||
}); |
||||
|
||||
it("Alerts when the default graph is updated but not when another graph is", () => { |
||||
const callbackFunc = jest.fn(); |
||||
subscribableDatastet.on(defaultGraph(), callbackFunc); |
||||
subscribableDatastet.add(lickyNameQuad); |
||||
subscribableDatastet.add( |
||||
quad( |
||||
namedNode("https://example.com/books#Dumbledoor"), |
||||
namedNode("http://example.org/books#name"), |
||||
literal("Dubmledoor"), |
||||
namedNode("https://coolgraphs.com"), |
||||
), |
||||
); |
||||
expect(callbackFunc).toHaveBeenCalledTimes(1); |
||||
expect( |
||||
callbackFunc.mock.calls[0][0].equals( |
||||
createDataset([tomNameQuad, lickyNameQuad, tomTypeQuad]), |
||||
), |
||||
).toBe(true); |
||||
}); |
||||
|
||||
it("Alerts when a named graph is updated", () => { |
||||
const callbackFunc = jest.fn(); |
||||
subscribableDatastet.on(namedNode("https://coolgraphs.com"), callbackFunc); |
||||
const quadWithGraph = quad( |
||||
namedNode("https://example.com/books#Dumbledoor"), |
||||
namedNode("http://example.org/books#name"), |
||||
literal("Dubmledoor"), |
||||
namedNode("https://coolgraphs.com"), |
||||
); |
||||
subscribableDatastet.add(quadWithGraph); |
||||
expect(callbackFunc).toHaveBeenCalledTimes(1); |
||||
expect( |
||||
callbackFunc.mock.calls[0][0].equals(createDataset([quadWithGraph])), |
||||
).toBe(true); |
||||
}); |
||||
|
||||
it("Alerts when one blank node is updated, but not the other", () => { |
||||
const blankNodeQuadA = quad( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
namedNode("http://example.org/cartoons#Address"), |
||||
blankNode(), |
||||
); |
||||
const blankNodeQuadB = quad( |
||||
namedNode("http://example.org/cartoons#Licky"), |
||||
namedNode("http://example.org/cartoons#Address"), |
||||
blankNode(), |
||||
); |
||||
subscribableDatastet.addAll([blankNodeQuadA, blankNodeQuadB]); |
||||
const callbackFunc = jest.fn(); |
||||
subscribableDatastet.on(blankNodeQuadA.object as BlankNode, callbackFunc); |
||||
const blankNodeAdditionA = quad( |
||||
blankNodeQuadA.object as BlankNode, |
||||
namedNode("http://example.org/cartoons#StreetNumber"), |
||||
literal("1234"), |
||||
); |
||||
subscribableDatastet.add(blankNodeAdditionA); |
||||
const blankNodeAdditionB = quad( |
||||
blankNodeQuadB.object as BlankNode, |
||||
namedNode("http://example.org/cartoons#StreetNumber"), |
||||
literal("65"), |
||||
); |
||||
subscribableDatastet.add(blankNodeAdditionB); |
||||
expect(callbackFunc).toBeCalledTimes(1); |
||||
expect( |
||||
callbackFunc.mock.calls[0][0].equals( |
||||
createDataset([blankNodeQuadA, blankNodeAdditionA]), |
||||
), |
||||
).toBe(true); |
||||
}); |
||||
|
||||
it("Throws an error if you try to subscribe to an invalid node type", () => { |
||||
expect(() => |
||||
// Used incorrect parameter for test
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
subscribableDatastet.on(literal("YOLO"), () => { |
||||
/* Do nothing */ |
||||
}), |
||||
).toThrowError("Invalid term type for subscription"); |
||||
}); |
||||
|
||||
it("Provides event names", () => { |
||||
const sampleBlankNode = blankNode(); |
||||
subscribableDatastet.on(namedNode("https://example.com"), () => { |
||||
/* Do nothing */ |
||||
}); |
||||
subscribableDatastet.on(sampleBlankNode, () => { |
||||
/* Do nothing */ |
||||
}); |
||||
subscribableDatastet.on(defaultGraph(), () => { |
||||
/* Do nothing */ |
||||
}); |
||||
const subscribableTerms = subscribableDatastet.eventNames(); |
||||
expect(subscribableTerms.length).toBe(3); |
||||
expect( |
||||
subscribableTerms.some((curTerm) => |
||||
curTerm.equals(namedNode("https://example.com")), |
||||
), |
||||
).toBe(true); |
||||
expect( |
||||
subscribableTerms.some((curTerm) => curTerm.equals(sampleBlankNode)), |
||||
).toBe(true); |
||||
expect( |
||||
subscribableTerms.some((curTerm) => curTerm.equals(defaultGraph())), |
||||
).toBe(true); |
||||
}); |
||||
|
||||
it("Throws an error if somehow a bad key is registered to an event emitter", () => { |
||||
// Disable trypscript to set up a private variable you shouldn't be able to access
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
subscribableDatastet.eventEmitter.on("Blah Blah Blah", () => { |
||||
/* Do Nothing */ |
||||
}); |
||||
expect(() => subscribableDatastet.eventNames()).toThrowError( |
||||
"Invalid Subscription Key", |
||||
); |
||||
}); |
||||
|
||||
it("Gets the max listeners", () => { |
||||
expect(subscribableDatastet.getMaxListeners()).toBe(10); |
||||
}); |
||||
|
||||
it("Gets the current Listeners for a specific name", () => { |
||||
const dummyListener1 = () => { |
||||
/* Do Nothing */ |
||||
}; |
||||
const dummyListener2 = () => { |
||||
/* Do Nothing */ |
||||
}; |
||||
subscribableDatastet.on( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
dummyListener1, |
||||
); |
||||
subscribableDatastet.on( |
||||
namedNode("http://example.org/cartoons#Licky"), |
||||
dummyListener2, |
||||
); |
||||
const listeners = subscribableDatastet.listeners( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
); |
||||
expect(listeners.length).toBe(1); |
||||
expect(listeners[0]).toBe(dummyListener1); |
||||
}); |
||||
|
||||
it("Unsubscribes from a listener", () => { |
||||
const callbackFunc = jest.fn(); |
||||
subscribableDatastet.on( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
callbackFunc, |
||||
); |
||||
subscribableDatastet.off( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
callbackFunc, |
||||
); |
||||
subscribableDatastet.add(tomColorQuad); |
||||
expect(callbackFunc).toHaveBeenCalledTimes(0); |
||||
}); |
||||
|
||||
it("Runs 'once' without erroring", () => { |
||||
expect( |
||||
subscribableDatastet.once( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
() => { |
||||
/* Do Nothing */ |
||||
}, |
||||
), |
||||
).toBe(subscribableDatastet); |
||||
}); |
||||
|
||||
it("Runs 'prependListener' without erroring", () => { |
||||
expect( |
||||
subscribableDatastet.prependListener( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
() => { |
||||
/* Do Nothing */ |
||||
}, |
||||
), |
||||
).toBe(subscribableDatastet); |
||||
}); |
||||
|
||||
it("Runs the 'prependOnceListener' without erroring", () => { |
||||
expect( |
||||
subscribableDatastet.prependOnceListener( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
() => { |
||||
/* Do Nothing */ |
||||
}, |
||||
), |
||||
).toBe(subscribableDatastet); |
||||
}); |
||||
|
||||
it("Removes all listeners", () => { |
||||
const dummyListener1 = () => { |
||||
/* Do Nothing */ |
||||
}; |
||||
const dummyListener2 = () => { |
||||
/* Do Nothing */ |
||||
}; |
||||
subscribableDatastet.on( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
dummyListener1, |
||||
); |
||||
subscribableDatastet.on( |
||||
namedNode("http://example.org/cartoons#Licky"), |
||||
dummyListener2, |
||||
); |
||||
subscribableDatastet.removeAllListeners( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
); |
||||
expect( |
||||
subscribableDatastet.listenerCount( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
), |
||||
).toBe(0); |
||||
expect( |
||||
subscribableDatastet.listenerCount( |
||||
namedNode("http://example.org/cartoons#Licky"), |
||||
), |
||||
).toBe(1); |
||||
}); |
||||
|
||||
it("Sets max listeners", () => { |
||||
subscribableDatastet.setMaxListeners(20); |
||||
expect(subscribableDatastet.getMaxListeners()).toBe(20); |
||||
}); |
||||
|
||||
it("Returns raw listeners", () => { |
||||
const dummyListener1 = () => { |
||||
/* Do Nothing */ |
||||
}; |
||||
subscribableDatastet.on( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
dummyListener1, |
||||
); |
||||
const rawListeners = subscribableDatastet.rawListeners( |
||||
namedNode("http://example.org/cartoons#Tom"), |
||||
); |
||||
expect(rawListeners.length).toBe(1); |
||||
expect(rawListeners[0]).toBe(dummyListener1); |
||||
}); |
||||
|
||||
it("Returns a transaction", () => { |
||||
expect( |
||||
subscribableDatastet.startTransaction() instanceof |
||||
ProxyTransactionalDataset, |
||||
).toBe(true); |
||||
}); |
||||
}); |
@ -0,0 +1,19 @@ |
||||
import { serializedToSubscribableDataset } from "../src"; |
||||
import { turtleData, jsonLdData } from "@ldo/dataset/test/sampleData"; |
||||
|
||||
describe("createExtendedDatasetFromSerializedInput", () => { |
||||
it("creates a dataset with turtle", async () => { |
||||
const dataset = await serializedToSubscribableDataset(turtleData); |
||||
expect(dataset.size).toBe(9); |
||||
}); |
||||
|
||||
it.skip("creates a dataset with json-ld", async () => { |
||||
const dataset = await serializedToSubscribableDataset( |
||||
JSON.stringify(jsonLdData), |
||||
{ |
||||
format: "application/json-ld", |
||||
}, |
||||
); |
||||
expect(dataset.size).toBe(9); |
||||
}); |
||||
}); |
@ -0,0 +1,19 @@ |
||||
import { |
||||
createSubscribableDataset, |
||||
createSubscribableDatasetFactory, |
||||
serializedToSubscribableDataset, |
||||
ProxyTransactionalDataset, |
||||
WrapperSubscribableDataset, |
||||
WrapperSubscribableDatasetFactory, |
||||
} from "../src"; |
||||
|
||||
describe("Exports", () => { |
||||
it("Has all exports", () => { |
||||
expect(createSubscribableDataset); |
||||
expect(ProxyTransactionalDataset); |
||||
expect(WrapperSubscribableDataset); |
||||
expect(WrapperSubscribableDatasetFactory); |
||||
expect(serializedToSubscribableDataset); |
||||
expect(createSubscribableDatasetFactory); |
||||
}); |
||||
}); |
@ -0,0 +1,7 @@ |
||||
{ |
||||
"extends": "../../tsconfig.base.json", |
||||
"compilerOptions": { |
||||
"outDir": "./dist" |
||||
}, |
||||
"include": ["./src"] |
||||
} |
Loading…
Reference in new issue