Merge pull request #11 from o-development/transactionLdoDataset

Transaction ldo dataset
main
jaxoncreed 2 years ago committed by GitHub
commit b0956fc67b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      lerna.json
  2. 36
      package-lock.json
  3. 2
      packages/cli/package.json
  4. 6
      packages/demo-react/package.json
  5. 4
      packages/jsonld-dataset-proxy/package.json
  6. 8
      packages/jsonld-dataset-proxy/src/arrayProxy/modifyArray.ts
  7. 6
      packages/ldo/package.json
  8. 17
      packages/ldo/src/LdoDataset.ts
  9. 45
      packages/ldo/src/LdoDatasetFactory.ts
  10. 19
      packages/ldo/src/LdoTransactionDataset.ts
  11. 6
      packages/ldo/src/createLdoDataset.ts
  12. 1
      packages/ldo/src/index.ts
  13. 9
      packages/ldo/src/types.ts
  14. 15
      packages/ldo/src/util.ts
  15. 21
      packages/ldo/test/TransactionLdoDataset.test.ts
  16. 4
      packages/ldo/test/methods.test.ts
  17. 10
      packages/solid-react/package.json
  18. 10
      packages/solid-react/src/useLdoMethods.ts
  19. 6
      packages/solid/package.json
  20. 119
      packages/solid/src/SolidLdoDataset.ts
  21. 179
      packages/solid/src/SolidLdoTransactionDataset.ts
  22. 2
      packages/solid/src/createSolidLdoDataset.ts
  23. 1
      packages/solid/src/index.ts
  24. 26
      packages/solid/src/methods.ts
  25. 4
      packages/solid/src/requester/requests/requestOptions.ts
  26. 14
      packages/solid/src/types.ts
  27. 231
      packages/solid/test/Integration.test.ts
  28. 4
      packages/solid/test/solidServer.helper.ts
  29. 2
      packages/subscribable-dataset/package.json
  30. 271
      packages/subscribable-dataset/src/SubscribableDataset.ts
  31. 31
      packages/subscribable-dataset/src/SubscribableDatasetFactory.ts
  32. 49
      packages/subscribable-dataset/src/TransactionDataset.ts
  33. 27
      packages/subscribable-dataset/src/TransactionDatasetFactory.ts
  34. 27
      packages/subscribable-dataset/src/WrapperSubscribableDatasetFactory.ts
  35. 46
      packages/subscribable-dataset/src/createSubscribableDataset.ts
  36. 10
      packages/subscribable-dataset/src/createSubscribableDatasetFromSerializedInput.ts
  37. 30
      packages/subscribable-dataset/src/createWrapperSubscribableDataset.ts
  38. 15
      packages/subscribable-dataset/src/index.ts
  39. 47
      packages/subscribable-dataset/src/types.ts
  40. 27
      packages/subscribable-dataset/src/util.ts
  41. 11
      packages/subscribable-dataset/test/SubscribableDataset.test.ts
  42. 47
      packages/subscribable-dataset/test/TransactionalDataset.test.ts
  43. 0
      packages/subscribable-dataset/test/createSubscribableDatasetFromSerializedInput.test.ts
  44. 14
      packages/subscribable-dataset/test/index.test.ts

@ -1,4 +1,4 @@
{ {
"$schema": "node_modules/lerna/schemas/lerna-schema.json", "$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "0.0.1-alpha.18" "version": "0.0.1-alpha.19"
} }

36
package-lock.json generated

@ -22784,7 +22784,7 @@
}, },
"packages/cli": { "packages/cli": {
"name": "@ldo/cli", "name": "@ldo/cli",
"version": "0.0.1-alpha.18", "version": "0.0.1-alpha.19",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ldo/schema-converter-shex": "^0.0.1-alpha.17", "@ldo/schema-converter-shex": "^0.0.1-alpha.17",
@ -23006,10 +23006,10 @@
}, },
"packages/demo-react": { "packages/demo-react": {
"name": "@ldo/demo-react", "name": "@ldo/demo-react",
"version": "0.0.1-alpha.18", "version": "0.0.1-alpha.19",
"dependencies": { "dependencies": {
"@inrupt/solid-client-authn-browser": "^2.0.0", "@inrupt/solid-client-authn-browser": "^2.0.0",
"@ldo/solid-react": "^0.0.1-alpha.18", "@ldo/solid-react": "^0.0.1-alpha.19",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.15.0", "react-router-dom": "^6.15.0",
@ -23018,7 +23018,7 @@
}, },
"devDependencies": { "devDependencies": {
"@craco/craco": "^7.1.0", "@craco/craco": "^7.1.0",
"@ldo/cli": "^0.0.1-alpha.18", "@ldo/cli": "^0.0.1-alpha.19",
"@types/jsonld": "^1.5.9", "@types/jsonld": "^1.5.9",
"@types/react": "^18.2.21", "@types/react": "^18.2.21",
"@types/shexj": "^2.1.4", "@types/shexj": "^2.1.4",
@ -23197,11 +23197,11 @@
}, },
"packages/jsonld-dataset-proxy": { "packages/jsonld-dataset-proxy": {
"name": "@ldo/jsonld-dataset-proxy", "name": "@ldo/jsonld-dataset-proxy",
"version": "0.0.1-alpha.18", "version": "0.0.1-alpha.19",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ldo/rdf-utils": "^0.0.1-alpha.17", "@ldo/rdf-utils": "^0.0.1-alpha.17",
"@ldo/subscribable-dataset": "^0.0.1-alpha.18", "@ldo/subscribable-dataset": "^0.0.1-alpha.19",
"@rdfjs/data-model": "^1.2.0", "@rdfjs/data-model": "^1.2.0",
"@rdfjs/dataset": "^1.1.0", "@rdfjs/dataset": "^1.1.0",
"jsonld2graphobject": "^0.0.4" "jsonld2graphobject": "^0.0.4"
@ -23277,12 +23277,12 @@
}, },
"packages/ldo": { "packages/ldo": {
"name": "@ldo/ldo", "name": "@ldo/ldo",
"version": "0.0.1-alpha.18", "version": "0.0.1-alpha.19",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ldo/dataset": "^0.0.1-alpha.17", "@ldo/dataset": "^0.0.1-alpha.17",
"@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.18", "@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.19",
"@ldo/subscribable-dataset": "^0.0.1-alpha.18", "@ldo/subscribable-dataset": "^0.0.1-alpha.19",
"@rdfjs/data-model": "^1.2.0", "@rdfjs/data-model": "^1.2.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"readable-stream": "^4.3.0" "readable-stream": "^4.3.0"
@ -24561,12 +24561,12 @@
}, },
"packages/solid": { "packages/solid": {
"name": "@ldo/solid", "name": "@ldo/solid",
"version": "0.0.1-alpha.18", "version": "0.0.1-alpha.19",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@inrupt/solid-client": "^1.30.0", "@inrupt/solid-client": "^1.30.0",
"@ldo/dataset": "^0.0.1-alpha.17", "@ldo/dataset": "^0.0.1-alpha.17",
"@ldo/ldo": "^0.0.1-alpha.18", "@ldo/ldo": "^0.0.1-alpha.19",
"@ldo/rdf-utils": "^0.0.1-alpha.17", "@ldo/rdf-utils": "^0.0.1-alpha.17",
"@types/parse-link-header": "^2.0.1", "@types/parse-link-header": "^2.0.1",
"cross-fetch": "^3.1.6", "cross-fetch": "^3.1.6",
@ -24575,7 +24575,7 @@
}, },
"devDependencies": { "devDependencies": {
"@inrupt/solid-client-authn-core": "^1.17.1", "@inrupt/solid-client-authn-core": "^1.17.1",
"@ldo/cli": "^0.0.1-alpha.18", "@ldo/cli": "^0.0.1-alpha.19",
"@rdfjs/data-model": "^1.2.0", "@rdfjs/data-model": "^1.2.0",
"@rdfjs/types": "^1.0.1", "@rdfjs/types": "^1.0.1",
"@solid/community-server": "^6.0.2", "@solid/community-server": "^6.0.2",
@ -24591,15 +24591,15 @@
}, },
"packages/solid-react": { "packages/solid-react": {
"name": "@ldo/solid-react", "name": "@ldo/solid-react",
"version": "0.0.1-alpha.18", "version": "0.0.1-alpha.19",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@inrupt/solid-client": "^2.0.0", "@inrupt/solid-client": "^2.0.0",
"@ldo/dataset": "^0.0.1-alpha.17", "@ldo/dataset": "^0.0.1-alpha.17",
"@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.18", "@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.19",
"@ldo/ldo": "^0.0.1-alpha.18", "@ldo/ldo": "^0.0.1-alpha.19",
"@ldo/solid": "^0.0.1-alpha.18", "@ldo/solid": "^0.0.1-alpha.19",
"@ldo/subscribable-dataset": "^0.0.1-alpha.18", "@ldo/subscribable-dataset": "^0.0.1-alpha.19",
"@rdfjs/data-model": "^1.2.0", "@rdfjs/data-model": "^1.2.0",
"cross-fetch": "^3.1.6" "cross-fetch": "^3.1.6"
}, },
@ -26819,7 +26819,7 @@
}, },
"packages/subscribable-dataset": { "packages/subscribable-dataset": {
"name": "@ldo/subscribable-dataset", "name": "@ldo/subscribable-dataset",
"version": "0.0.1-alpha.18", "version": "0.0.1-alpha.19",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ldo/dataset": "^0.0.1-alpha.17", "@ldo/dataset": "^0.0.1-alpha.17",

@ -1,6 +1,6 @@
{ {
"name": "@ldo/cli", "name": "@ldo/cli",
"version": "0.0.1-alpha.18", "version": "0.0.1-alpha.19",
"description": "A Command Line Interface for Linked Data Objects", "description": "A Command Line Interface for Linked Data Objects",
"main": "./dist/index.js", "main": "./dist/index.js",
"bin": { "bin": {

@ -1,9 +1,9 @@
{ {
"name": "@ldo/demo-react", "name": "@ldo/demo-react",
"version": "0.0.1-alpha.18", "version": "0.0.1-alpha.19",
"dependencies": { "dependencies": {
"@inrupt/solid-client-authn-browser": "^2.0.0", "@inrupt/solid-client-authn-browser": "^2.0.0",
"@ldo/solid-react": "^0.0.1-alpha.18", "@ldo/solid-react": "^0.0.1-alpha.19",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.15.0", "react-router-dom": "^6.15.0",
@ -37,7 +37,7 @@
}, },
"devDependencies": { "devDependencies": {
"@craco/craco": "^7.1.0", "@craco/craco": "^7.1.0",
"@ldo/cli": "^0.0.1-alpha.18", "@ldo/cli": "^0.0.1-alpha.19",
"@types/jsonld": "^1.5.9", "@types/jsonld": "^1.5.9",
"@types/react": "^18.2.21", "@types/react": "^18.2.21",
"@types/shexj": "^2.1.4", "@types/shexj": "^2.1.4",

@ -1,6 +1,6 @@
{ {
"name": "@ldo/jsonld-dataset-proxy", "name": "@ldo/jsonld-dataset-proxy",
"version": "0.0.1-alpha.18", "version": "0.0.1-alpha.19",
"description": "", "description": "",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
@ -41,7 +41,7 @@
], ],
"dependencies": { "dependencies": {
"@ldo/rdf-utils": "^0.0.1-alpha.17", "@ldo/rdf-utils": "^0.0.1-alpha.17",
"@ldo/subscribable-dataset": "^0.0.1-alpha.18", "@ldo/subscribable-dataset": "^0.0.1-alpha.19",
"@rdfjs/data-model": "^1.2.0", "@rdfjs/data-model": "^1.2.0",
"@rdfjs/dataset": "^1.1.0", "@rdfjs/dataset": "^1.1.0",
"jsonld2graphobject": "^0.0.4" "jsonld2graphobject": "^0.0.4"

@ -1,7 +1,10 @@
import { defaultGraph } from "@rdfjs/data-model"; import { defaultGraph } from "@rdfjs/data-model";
import type { Quad } from "@rdfjs/types"; import type { Quad } from "@rdfjs/types";
import type { ObjectNode } from "@ldo/rdf-utils"; import type { ObjectNode } from "@ldo/rdf-utils";
import { ProxyTransactionalDataset } from "@ldo/subscribable-dataset"; import {
TransactionDataset,
createTransactionDatasetFactory,
} from "@ldo/subscribable-dataset";
import { createDatasetFactory } from "@ldo/dataset"; import { createDatasetFactory } from "@ldo/dataset";
import type { ProxyContext } from "../ProxyContext"; import type { ProxyContext } from "../ProxyContext";
import { addObjectToDataset } from "../util/addObjectToDataset"; import { addObjectToDataset } from "../util/addObjectToDataset";
@ -31,9 +34,10 @@ export function checkArrayModification(
); );
} }
// Create a test dataset to see if the inputted data is valid // Create a test dataset to see if the inputted data is valid
const testDataset = new ProxyTransactionalDataset( const testDataset = new TransactionDataset(
proxyContext.dataset, proxyContext.dataset,
createDatasetFactory(), createDatasetFactory(),
createTransactionDatasetFactory(),
); );
addObjectToDataset( addObjectToDataset(
objectToAdd as RawObject, objectToAdd as RawObject,

@ -1,6 +1,6 @@
{ {
"name": "@ldo/ldo", "name": "@ldo/ldo",
"version": "0.0.1-alpha.18", "version": "0.0.1-alpha.19",
"description": "", "description": "",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
@ -39,8 +39,8 @@
}, },
"dependencies": { "dependencies": {
"@ldo/dataset": "^0.0.1-alpha.17", "@ldo/dataset": "^0.0.1-alpha.17",
"@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.18", "@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.19",
"@ldo/subscribable-dataset": "^0.0.1-alpha.18", "@ldo/subscribable-dataset": "^0.0.1-alpha.19",
"@rdfjs/data-model": "^1.2.0", "@rdfjs/data-model": "^1.2.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"readable-stream": "^4.3.0" "readable-stream": "^4.3.0"

@ -1,9 +1,11 @@
import type { Quad } from "@rdfjs/types"; import type { Quad } from "@rdfjs/types";
import jsonldDatasetProxy from "@ldo/jsonld-dataset-proxy"; import jsonldDatasetProxy from "@ldo/jsonld-dataset-proxy";
import { WrapperSubscribableDataset } from "@ldo/subscribable-dataset"; import { SubscribableDataset } from "@ldo/subscribable-dataset";
import { LdoBuilder } from "./LdoBuilder"; import { LdoBuilder } from "./LdoBuilder";
import type { ShapeType } from "./ShapeType"; import type { ShapeType } from "./ShapeType";
import type { LdoBase } from "./index"; import type { LdoBase } from "./index";
import { LdoTransactionDataset } from "./LdoTransactionDataset";
import type { ILdoDataset } from "./types";
/** /**
* @category Getting an LdoDataset * @category Getting an LdoDataset
@ -22,7 +24,10 @@ import type { LdoBase } from "./index";
* const ldoBuilder = ldoDataset.usingType(FoafProfileShapeType); * const ldoBuilder = ldoDataset.usingType(FoafProfileShapeType);
* ``` * ```
*/ */
export class LdoDataset extends WrapperSubscribableDataset<Quad> { export class LdoDataset
extends SubscribableDataset<Quad>
implements ILdoDataset
{
/** /**
* Creates an LdoBuilder for a given shapeType * Creates an LdoBuilder for a given shapeType
* *
@ -35,4 +40,12 @@ export class LdoDataset extends WrapperSubscribableDataset<Quad> {
const proxyBuilder = jsonldDatasetProxy(this, shapeType.context); const proxyBuilder = jsonldDatasetProxy(this, shapeType.context);
return new LdoBuilder(proxyBuilder, shapeType); return new LdoBuilder(proxyBuilder, shapeType);
} }
public startTransaction(): LdoTransactionDataset {
return new LdoTransactionDataset(
this,
this.datasetFactory,
this.transactionDatasetFactory,
);
}
} }

@ -1,4 +1,6 @@
import type { DatasetFactory, Dataset, Quad } from "@rdfjs/types"; import type { Dataset, Quad } from "@rdfjs/types";
import type { ISubscribableDatasetFactory } from "@ldo/subscribable-dataset";
import { SubscribableDatasetFactory } from "@ldo/subscribable-dataset";
import { LdoDataset } from "./LdoDataset"; import { LdoDataset } from "./LdoDataset";
/** /**
@ -9,37 +11,28 @@ import { LdoDataset } from "./LdoDataset";
* *
* @example * @example
* ```typescript * ```typescript
* import { createLdoDatasetFactory } from "ldo"; * import { createLdoDatasetFactory } from "@ldo/ldo";
* import { createExtendedDatasetFactory } from "@ldo/dataset";
* import { createTransactionDatasetFactory } from "@ldo/subscribable-dataset";
* *
* const datasetFactory = // some RDF/JS Dataset Factory * const datasetFactory = createExtendedDatasetFactory();
* const ldoDatasetFactory = new LdoDatasetFactory(datasetFactory); * const transactionDatasetFactory = createTransactionDatasetFactroy();
* const ldoDatasetFactory = new LdoDatasetFactory(
* datasetFactory,
* transactionDatasetFactory
* );
* const ldoDataset = ldoDatasetFactory.dataset(initialDataset); * const ldoDataset = ldoDatasetFactory.dataset(initialDataset);
* ``` * ```
*/ */
export class LdoDatasetFactory implements DatasetFactory<Quad, Quad> { export class LdoDatasetFactory
private datasetFactory: DatasetFactory<Quad, Quad>; extends SubscribableDatasetFactory<Quad>
implements ISubscribableDatasetFactory<Quad>
/** {
* @constructor dataset(quads?: Dataset<Quad, Quad> | Quad[] | undefined): LdoDataset {
* @param datasetFactory - A generic dataset factory this factory will wrap
*/
constructor(datasetFactory: DatasetFactory<Quad, Quad>) {
this.datasetFactory = datasetFactory;
}
/**
* Creates an LdoDataset
* @param quads - A list of quads to initialize the dataset
* @returns an LdoDataset
*/
dataset(quads?: Dataset<Quad, Quad> | Quad[]): LdoDataset {
return new LdoDataset( return new LdoDataset(
this.datasetFactory, this.datasetFactory,
quads this.transactionDatasetFactory,
? Array.isArray(quads) this.datasetFactory.dataset(quads),
? this.datasetFactory.dataset(quads)
: quads
: undefined,
); );
} }
} }

@ -0,0 +1,19 @@
import { TransactionDataset } from "@ldo/subscribable-dataset";
import type { Quad } from "@rdfjs/types";
import type { ILdoDataset } from "./types";
import { LdoBuilder } from "./LdoBuilder";
import type { ShapeType } from "./ShapeType";
import type { LdoBase } from "./util";
import jsonldDatasetProxy from "@ldo/jsonld-dataset-proxy";
export class LdoTransactionDataset
extends TransactionDataset<Quad>
implements ILdoDataset
{
usingType<Type extends LdoBase>(
shapeType: ShapeType<Type>,
): LdoBuilder<Type> {
const proxyBuilder = jsonldDatasetProxy(this, shapeType.context);
return new LdoBuilder(proxyBuilder, shapeType);
}
}

@ -1,6 +1,7 @@
import type { Dataset, DatasetFactory, Quad } from "@rdfjs/types"; import type { Dataset, DatasetFactory, Quad } from "@rdfjs/types";
import { createDataset } from "@ldo/dataset"; import { createDataset } from "@ldo/dataset";
import { LdoDatasetFactory } from "./LdoDatasetFactory"; import { LdoDatasetFactory } from "./LdoDatasetFactory";
import { createTransactionDatasetFactory } from "@ldo/subscribable-dataset";
import type { LdoDataset } from "./LdoDataset"; import type { LdoDataset } from "./LdoDataset";
/** /**
@ -22,7 +23,10 @@ export function createLdoDatasetFactory() {
return createDataset(quads); return createDataset(quads);
}, },
}; };
return new LdoDatasetFactory(datasetFactory); return new LdoDatasetFactory(
datasetFactory,
createTransactionDatasetFactory(),
);
} }
/** /**

@ -2,6 +2,7 @@ export * from "./parseRdf";
export * from "./ShapeType"; export * from "./ShapeType";
export * from "./methods"; export * from "./methods";
export * from "./LdoDataset"; export * from "./LdoDataset";
export * from "./LdoTransactionDataset";
export * from "./LdoBuilder"; export * from "./LdoBuilder";
export * from "./createLdoDataset"; export * from "./createLdoDataset";
import type { LdoBase as LdoBaseImport } from "./util"; import type { LdoBase as LdoBaseImport } from "./util";

@ -0,0 +1,9 @@
import type { ISubscribableDataset } from "@ldo/subscribable-dataset";
import type { LdoBuilder } from "./LdoBuilder";
import type { ShapeType } from "./ShapeType";
import type { LdoBase } from "./util";
import type { Quad } from "@rdfjs/types";
export interface ILdoDataset extends ISubscribableDataset<Quad> {
usingType<Type extends LdoBase>(shapeType: ShapeType<Type>): LdoBuilder<Type>;
}

@ -8,14 +8,15 @@ import {
} from "@ldo/jsonld-dataset-proxy"; } from "@ldo/jsonld-dataset-proxy";
import type { AnyNode } from "@ldo/rdf-utils"; import type { AnyNode } from "@ldo/rdf-utils";
import type { import type {
SubscribableDataset, ISubscribableDataset,
TransactionalDataset, ITransactionDataset,
} from "@ldo/subscribable-dataset"; } from "@ldo/subscribable-dataset";
/** /**
* @category Types * @category Types
* `LdoBase` is an interface defining that a Linked Data Object is a JavaScript Object Literal. * `LdoBase` is an interface defining that a Linked Data Object is a JavaScript Object Literal.
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type LdoBase = Record<string, any>; export type LdoBase = Record<string, any>;
/** /**
@ -42,21 +43,21 @@ export function normalizeNodeNames<NodeType extends AnyNode>(
export function canDatasetStartTransaction( export function canDatasetStartTransaction(
dataset: Dataset, dataset: Dataset,
): dataset is SubscribableDataset<Quad> { ): dataset is ISubscribableDataset<Quad> {
return ( return (
typeof (dataset as SubscribableDataset).startTransaction === "function" typeof (dataset as ISubscribableDataset).startTransaction === "function"
); );
} }
export function isTransactionalDataset( export function isTransactionalDataset(
dataset: Dataset, dataset: Dataset,
): dataset is TransactionalDataset<Quad> { ): dataset is ITransactionDataset<Quad> {
return typeof (dataset as TransactionalDataset).commit === "function"; return typeof (dataset as ITransactionDataset).commit === "function";
} }
export function getTransactionalDatasetFromLdo( export function getTransactionalDatasetFromLdo(
ldo: LdoBase, ldo: LdoBase,
): [TransactionalDataset<Quad>, SubjectProxy | ArrayProxy] { ): [ITransactionDataset<Quad>, SubjectProxy | ArrayProxy] {
const proxy = getProxyFromObject(ldo); const proxy = getProxyFromObject(ldo);
const dataset = proxy[_getUnderlyingDataset]; const dataset = proxy[_getUnderlyingDataset];
if ( if (

@ -0,0 +1,21 @@
import { createLdoDataset } from "../src/createLdoDataset";
import { ProfileShapeType } from "./profileData";
describe("TransactionLdoDataset", () => {
it("Uses transactions with an LdoBuilder", () => {
const ldoDataset = createLdoDataset();
const transaction = ldoDataset.startTransaction();
const profile = transaction
.usingType(ProfileShapeType)
.fromSubject("https://example.com/Person1");
profile.fn = "John Doe";
expect(transaction.getChanges().added?.toString()).toBe(
'<https://example.com/Person1> <http://www.w3.org/2006/vcard/ns#fn> "John Doe" .\n',
);
expect(ldoDataset.toString()).toBe("");
transaction.commit();
expect(ldoDataset.toString()).toBe(
'<https://example.com/Person1> <http://www.w3.org/2006/vcard/ns#fn> "John Doe" .\n',
);
});
});

@ -9,7 +9,6 @@ import {
import { createDataset } from "@ldo/dataset"; import { createDataset } from "@ldo/dataset";
import type { SolidProfileShape } from "./profileData"; import type { SolidProfileShape } from "./profileData";
import { ProfileShapeType } from "./profileData"; import { ProfileShapeType } from "./profileData";
import type { LdoDataset } from "../src";
import { import {
commitTransaction, commitTransaction,
createLdoDataset, createLdoDataset,
@ -25,9 +24,10 @@ import {
setLanguagePreferences, setLanguagePreferences,
languagesOf, languagesOf,
} from "../src"; } from "../src";
import type { ILdoDataset } from "../src/types";
describe("methods", () => { describe("methods", () => {
let dataset: LdoDataset; let dataset: ILdoDataset;
let profile: SolidProfileShape; let profile: SolidProfileShape;
beforeEach(() => { beforeEach(() => {
dataset = createLdoDataset(); dataset = createLdoDataset();

@ -1,6 +1,6 @@
{ {
"name": "@ldo/solid-react", "name": "@ldo/solid-react",
"version": "0.0.1-alpha.18", "version": "0.0.1-alpha.19",
"description": "A React library for LDO and Solid", "description": "A React library for LDO and Solid",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
@ -38,10 +38,10 @@
"dependencies": { "dependencies": {
"@inrupt/solid-client": "^2.0.0", "@inrupt/solid-client": "^2.0.0",
"@ldo/dataset": "^0.0.1-alpha.17", "@ldo/dataset": "^0.0.1-alpha.17",
"@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.18", "@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.19",
"@ldo/ldo": "^0.0.1-alpha.18", "@ldo/ldo": "^0.0.1-alpha.19",
"@ldo/solid": "^0.0.1-alpha.18", "@ldo/solid": "^0.0.1-alpha.19",
"@ldo/subscribable-dataset": "^0.0.1-alpha.18", "@ldo/subscribable-dataset": "^0.0.1-alpha.19",
"@rdfjs/data-model": "^1.2.0", "@rdfjs/data-model": "^1.2.0",
"cross-fetch": "^3.1.6" "cross-fetch": "^3.1.6"
}, },

@ -1,6 +1,10 @@
import type { LdoBase, ShapeType } from "@ldo/ldo"; import type { LdoBase, ShapeType } from "@ldo/ldo";
import type { SubjectNode } from "@ldo/rdf-utils"; import type { SubjectNode } from "@ldo/rdf-utils";
import type { Resource, SolidLdoDataset } from "@ldo/solid"; import type {
Resource,
SolidLdoDataset,
SolidLdoTransactionDataset,
} from "@ldo/solid";
import { changeData, commitData } from "@ldo/solid"; import { changeData, commitData } from "@ldo/solid";
export interface UseLdoMethods { export interface UseLdoMethods {
@ -21,7 +25,9 @@ export interface UseLdoMethods {
resource: Resource, resource: Resource,
...additionalResources: Resource[] ...additionalResources: Resource[]
): Type; ): Type;
commitData(input: LdoBase): ReturnType<SolidLdoDataset["commitChangesToPod"]>; commitData(
input: LdoBase,
): ReturnType<SolidLdoTransactionDataset["commitToPod"]>;
} }
export function createUseLdoMethods(dataset: SolidLdoDataset): UseLdoMethods { export function createUseLdoMethods(dataset: SolidLdoDataset): UseLdoMethods {

@ -1,6 +1,6 @@
{ {
"name": "@ldo/solid", "name": "@ldo/solid",
"version": "0.0.1-alpha.18", "version": "0.0.1-alpha.19",
"description": "A library for LDO and Solid", "description": "A library for LDO and Solid",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
@ -26,7 +26,7 @@
"homepage": "https://github.com/o-development/ldobjects/tree/main/packages/solid#readme", "homepage": "https://github.com/o-development/ldobjects/tree/main/packages/solid#readme",
"devDependencies": { "devDependencies": {
"@inrupt/solid-client-authn-core": "^1.17.1", "@inrupt/solid-client-authn-core": "^1.17.1",
"@ldo/cli": "^0.0.1-alpha.18", "@ldo/cli": "^0.0.1-alpha.19",
"@rdfjs/data-model": "^1.2.0", "@rdfjs/data-model": "^1.2.0",
"@rdfjs/types": "^1.0.1", "@rdfjs/types": "^1.0.1",
"@solid/community-server": "^6.0.2", "@solid/community-server": "^6.0.2",
@ -42,7 +42,7 @@
"dependencies": { "dependencies": {
"@inrupt/solid-client": "^1.30.0", "@inrupt/solid-client": "^1.30.0",
"@ldo/dataset": "^0.0.1-alpha.17", "@ldo/dataset": "^0.0.1-alpha.17",
"@ldo/ldo": "^0.0.1-alpha.18", "@ldo/ldo": "^0.0.1-alpha.19",
"@ldo/rdf-utils": "^0.0.1-alpha.17", "@ldo/rdf-utils": "^0.0.1-alpha.17",
"@types/parse-link-header": "^2.0.1", "@types/parse-link-header": "^2.0.1",
"cross-fetch": "^3.1.6", "cross-fetch": "^3.1.6",

@ -1,26 +1,14 @@
import type { LdoBase, ShapeType } from "@ldo/ldo"; import type { LdoBase, ShapeType } from "@ldo/ldo";
import { LdoDataset, startTransaction } from "@ldo/ldo"; import { LdoDataset, startTransaction } from "@ldo/ldo";
import type { DatasetChanges, GraphNode, SubjectNode } from "@ldo/rdf-utils";
import type { Dataset, DatasetFactory, Quad } from "@rdfjs/types"; import type { Dataset, DatasetFactory, Quad } from "@rdfjs/types";
import type {
UpdateResult,
UpdateResultError,
} from "./requester/requests/updateDataResource";
import { AggregateError } from "./requester/results/error/ErrorResult";
import { InvalidUriError } from "./requester/results/error/InvalidUriError";
import type { AggregateSuccess } from "./requester/results/success/SuccessResult";
import type {
UpdateDefaultGraphSuccess,
UpdateSuccess,
} from "./requester/results/success/UpdateSuccess";
import type { Container } from "./resource/Container"; import type { Container } from "./resource/Container";
import type { Leaf } from "./resource/Leaf"; import type { Leaf } from "./resource/Leaf";
import type { ResourceResult } from "./resource/resourceResult/ResourceResult";
import type { ResourceGetterOptions } from "./ResourceStore"; import type { ResourceGetterOptions } from "./ResourceStore";
import type { SolidLdoDatasetContext } from "./SolidLdoDatasetContext"; import type { SolidLdoDatasetContext } from "./SolidLdoDatasetContext";
import { splitChangesByGraph } from "./util/splitChangesByGraph";
import type { ContainerUri, LeafUri } from "./util/uriTypes"; import type { ContainerUri, LeafUri } from "./util/uriTypes";
import { isContainerUri } from "./util/uriTypes"; import { SolidLdoTransactionDataset } from "./SolidLdoTransactionDataset";
import type { ITransactionDatasetFactory } from "@ldo/subscribable-dataset";
import type { SubjectNode } from "@ldo/rdf-utils";
import type { Resource } from "./resource/Resource"; import type { Resource } from "./resource/Resource";
/** /**
@ -57,14 +45,16 @@ export class SolidLdoDataset extends LdoDataset {
/** /**
* @param context - SolidLdoDatasetContext * @param context - SolidLdoDatasetContext
* @param datasetFactory - An optional dataset factory * @param datasetFactory - An optional dataset factory
* @param transactionDatasetFactory - A factory for creating transaction datasets
* @param initialDataset - A set of triples to initialize this dataset * @param initialDataset - A set of triples to initialize this dataset
*/ */
constructor( constructor(
context: SolidLdoDatasetContext, context: SolidLdoDatasetContext,
datasetFactory: DatasetFactory, datasetFactory: DatasetFactory,
transactionDatasetFactory: ITransactionDatasetFactory<Quad>,
initialDataset?: Dataset, initialDataset?: Dataset,
) { ) {
super(datasetFactory, initialDataset); super(datasetFactory, transactionDatasetFactory, initialDataset);
this.context = context; this.context = context;
} }
@ -92,98 +82,13 @@ export class SolidLdoDataset extends LdoDataset {
return this.context.resourceStore.get(uri, options); return this.context.resourceStore.get(uri, options);
} }
/** public startTransaction(): SolidLdoTransactionDataset {
* Given dataset changes, commit all changes made to the proper place return new SolidLdoTransactionDataset(
* on Solid Pods. this,
* this.context,
* @param changes - A set of changes that should be applied to Solid Pods this.datasetFactory,
* this.transactionDatasetFactory,
* @returns an AggregateSuccess if successful and an AggregateError if not
*
* @example
* ```typescript
* const result = await solidLdoDataset.commitChangesToPod({
* added: createDataset([
* quad(namedNode("a"), namedNode("b"), namedNode("d"));
* ]),
* removed: createDataset([
* quad(namedNode("a"), namedNode("b"), namedNode("c"));
* ])
* });
* if (result.isError()) {
* // handle error
* }
* ```
*/
async commitChangesToPod(
changes: DatasetChanges<Quad>,
): Promise<
| AggregateSuccess<
ResourceResult<UpdateSuccess | UpdateDefaultGraphSuccess, Leaf>
>
| AggregateError<UpdateResultError | InvalidUriError>
> {
// Optimistically add changes to the datastore
// this.bulk(changes);
const changesByGraph = splitChangesByGraph(changes);
// Iterate through all changes by graph in
const results: [
GraphNode,
DatasetChanges<Quad>,
UpdateResult | InvalidUriError | UpdateDefaultGraphSuccess,
][] = await Promise.all(
Array.from(changesByGraph.entries()).map(
async ([graph, datasetChanges]) => {
if (graph.termType === "DefaultGraph") {
// Undefined means that this is the default graph
this.bulk(datasetChanges);
return [
graph,
datasetChanges,
{
type: "updateDefaultGraphSuccess",
isError: false,
} as UpdateDefaultGraphSuccess,
];
}
if (isContainerUri(graph.value)) {
return [
graph,
datasetChanges,
new InvalidUriError(
graph.value,
`Container URIs are not allowed for custom data.`,
),
];
}
const resource = this.getResource(graph.value as LeafUri);
return [graph, datasetChanges, await resource.update(datasetChanges)];
},
),
); );
// If one has errored, return error
const errors = results.filter((result) => result[2].isError);
if (errors.length > 0) {
return new AggregateError(
errors.map(
(result) => result[2] as UpdateResultError | InvalidUriError,
),
);
}
return {
isError: false,
type: "aggregateSuccess",
results: results
.map((result) => result[2])
.filter(
(result): result is ResourceResult<UpdateSuccess, Leaf> =>
result.type === "updateSuccess" ||
result.type === "updateDefaultGraphSuccess",
),
};
} }
/** /**

@ -0,0 +1,179 @@
import { LdoTransactionDataset } from "@ldo/ldo";
import type { ISolidLdoDataset } from "./types";
import type { ResourceGetterOptions } from "./ResourceStore";
import type { Container } from "./resource/Container";
import type { Leaf } from "./resource/Leaf";
import {
isContainerUri,
type ContainerUri,
type LeafUri,
} from "./util/uriTypes";
import type { SolidLdoDatasetContext } from "./SolidLdoDatasetContext";
import type { DatasetFactory, Quad } from "@rdfjs/types";
import {
updateDatasetInBulk,
type ITransactionDatasetFactory,
} from "@ldo/subscribable-dataset";
import type { SolidLdoDataset } from "./SolidLdoDataset";
import type { AggregateSuccess } from "./requester/results/success/SuccessResult";
import type { ResourceResult } from "./resource/resourceResult/ResourceResult";
import type {
UpdateDefaultGraphSuccess,
UpdateSuccess,
} from "./requester/results/success/UpdateSuccess";
import { AggregateError } from "./requester/results/error/ErrorResult";
import type {
UpdateResult,
UpdateResultError,
} from "./requester/requests/updateDataResource";
import { InvalidUriError } from "./requester/results/error/InvalidUriError";
import type { DatasetChanges, GraphNode } from "@ldo/rdf-utils";
import { splitChangesByGraph } from "./util/splitChangesByGraph";
/**
* A SolidLdoTransactionDataset has all the functionality of a SolidLdoDataset
* and represents a transaction to the parent SolidLdoDataset.
*
* It is recommended to use the `startTransaction` method on a SolidLdoDataset
* to initialize this class
*
* @example
* ```typescript
* import { createSolidLdoDataset } from "@ldo/solid";
* import { ProfileShapeType } from "./.ldo/profile.shapeTypes.ts"
*
* // ...
*
* const solidLdoDataset = createSolidLdoDataset();
*
* const profileDocument = solidLdoDataset
* .getResource("https://example.com/profile");
* await profileDocument.read();
*
* const transaction = solidLdoDataset.startTransaction();
*
* const profile = transaction
* .using(ProfileShapeType)
* .fromSubject("https://example.com/profile#me");
* profile.name = "Some Name";
* await transaction.commitToPod();
* ```
*/
export class SolidLdoTransactionDataset
extends LdoTransactionDataset
implements ISolidLdoDataset
{
/**
* @internal
*/
public context: SolidLdoDatasetContext;
/**
* @param context - SolidLdoDatasetContext
* @param datasetFactory - An optional dataset factory
* @param transactionDatasetFactory - A factory for creating transaction datasets
* @param initialDataset - A set of triples to initialize this dataset
*/
constructor(
parentDataset: SolidLdoDataset,
context: SolidLdoDatasetContext,
datasetFactory: DatasetFactory,
transactionDatasetFactory: ITransactionDatasetFactory<Quad>,
) {
super(parentDataset, datasetFactory, transactionDatasetFactory);
this.context = context;
}
/**
* Retireves a representation (either a LeafResource or a ContainerResource)
* of a Solid Resource at the given URI. This resource represents the
* current state of the resource: whether it is currently fetched or in the
* process of fetching as well as some information about it.
*
* @param uri - the URI of the resource
* @param options - Special options for getting the resource
*
* @returns a Leaf or Container Resource
*
* @example
* ```typescript
* const profileDocument = solidLdoDataset
* .getResource("https://example.com/profile");
* ```
*/
getResource(uri: ContainerUri, options?: ResourceGetterOptions): Container;
getResource(uri: LeafUri, options?: ResourceGetterOptions): Leaf;
getResource(uri: string, options?: ResourceGetterOptions): Leaf | Container;
getResource(uri: string, options?: ResourceGetterOptions): Leaf | Container {
return this.context.resourceStore.get(uri, options);
}
async commitToPod(): Promise<
| AggregateSuccess<
ResourceResult<UpdateSuccess | UpdateDefaultGraphSuccess, Leaf>
>
| AggregateError<UpdateResultError | InvalidUriError>
> {
const changes = this.getChanges();
const changesByGraph = splitChangesByGraph(changes);
// Iterate through all changes by graph in
const results: [
GraphNode,
DatasetChanges<Quad>,
UpdateResult | InvalidUriError | UpdateDefaultGraphSuccess,
][] = await Promise.all(
Array.from(changesByGraph.entries()).map(
async ([graph, datasetChanges]) => {
if (graph.termType === "DefaultGraph") {
// Undefined means that this is the default graph
updateDatasetInBulk(this.parentDataset, datasetChanges);
return [
graph,
datasetChanges,
{
type: "updateDefaultGraphSuccess",
isError: false,
} as UpdateDefaultGraphSuccess,
];
}
if (isContainerUri(graph.value)) {
return [
graph,
datasetChanges,
new InvalidUriError(
graph.value,
`Container URIs are not allowed for custom data.`,
),
];
}
const resource = this.getResource(graph.value as LeafUri);
const updateResult = await resource.update(datasetChanges);
return [graph, datasetChanges, updateResult];
},
),
);
// If one has errored, return error
const errors = results.filter((result) => result[2].isError);
if (errors.length > 0) {
return new AggregateError(
errors.map(
(result) => result[2] as UpdateResultError | InvalidUriError,
),
);
}
return {
isError: false,
type: "aggregateSuccess",
results: results
.map((result) => result[2])
.filter(
(result): result is ResourceResult<UpdateSuccess, Leaf> =>
result.type === "updateSuccess" ||
result.type === "updateDefaultGraphSuccess",
),
};
}
}

@ -5,6 +5,7 @@ import type { SolidLdoDatasetContext } from "./SolidLdoDatasetContext";
import { createDataset, createDatasetFactory } from "@ldo/dataset"; import { createDataset, createDatasetFactory } from "@ldo/dataset";
import { ResourceStore } from "./ResourceStore"; import { ResourceStore } from "./ResourceStore";
import { guaranteeFetch } from "./util/guaranteeFetch"; import { guaranteeFetch } from "./util/guaranteeFetch";
import { createTransactionDatasetFactory } from "@ldo/subscribable-dataset";
/** /**
* Options for createSolidDataset * Options for createSolidDataset
@ -56,6 +57,7 @@ export function createSolidLdoDataset(
const solidLdoDataset = new SolidLdoDataset( const solidLdoDataset = new SolidLdoDataset(
context, context,
finalDatasetFactory, finalDatasetFactory,
createTransactionDatasetFactory(),
finalDataset, finalDataset,
); );
const resourceStore = new ResourceStore(context); const resourceStore = new ResourceStore(context);

@ -1,6 +1,7 @@
export * from "./createSolidLdoDataset"; export * from "./createSolidLdoDataset";
export * from "./SolidLdoDataset"; export * from "./SolidLdoDataset";
export * from "./SolidLdoDatasetContext"; export * from "./SolidLdoDatasetContext";
export * from "./SolidLdoTransactionDataset";
export * from "./resource/Resource"; export * from "./resource/Resource";
export * from "./resource/Container"; export * from "./resource/Container";

@ -1,16 +1,9 @@
import { import { startTransaction, type LdoBase, write, getDataset } from "@ldo/ldo";
startTransaction,
type LdoBase,
write,
transactionChanges,
getDataset,
} from "@ldo/ldo";
import type { DatasetChanges } from "@ldo/rdf-utils";
import type { Resource } from "./resource/Resource"; import type { Resource } from "./resource/Resource";
import type { SolidLdoDataset } from "./SolidLdoDataset";
import type { Quad } from "@rdfjs/types"; import type { Quad } from "@rdfjs/types";
import { _proxyContext, getProxyFromObject } from "@ldo/jsonld-dataset-proxy"; import { _proxyContext, getProxyFromObject } from "@ldo/jsonld-dataset-proxy";
import type { SubscribableDataset } from "@ldo/subscribable-dataset"; import type { SubscribableDataset } from "@ldo/subscribable-dataset";
import type { SolidLdoTransactionDataset } from "./SolidLdoTransactionDataset";
/** /**
* Begins tracking changes to eventually commit. * Begins tracking changes to eventually commit.
@ -34,7 +27,7 @@ import type { SubscribableDataset } from "@ldo/subscribable-dataset";
* *
* const cProfile = changeData(profile, resource); * const cProfile = changeData(profile, resource);
* cProfile.name = "My New Name"; * cProfile.name = "My New Name";
* await commitData(cProfile); * const result = await commitData(cProfile);
* ``` * ```
*/ */
export function changeData<Type extends LdoBase>( export function changeData<Type extends LdoBase>(
@ -72,19 +65,20 @@ export function changeData<Type extends LdoBase>(
* *
* const cProfile = changeData(profile, resource); * const cProfile = changeData(profile, resource);
* cProfile.name = "My New Name"; * cProfile.name = "My New Name";
* await commitData(cProfile); * const result = await commitData(cProfile);
* ``` * ```
*/ */
export function commitData( export async function commitData(
input: LdoBase, input: LdoBase,
): ReturnType<SolidLdoDataset["commitChangesToPod"]> { ): ReturnType<SolidLdoTransactionDataset["commitToPod"]> {
const changes = transactionChanges(input); const transactionDataset = getDataset(input) as SolidLdoTransactionDataset;
const result = await transactionDataset.commitToPod();
if (result.isError) return result;
// Take the LdoProxy out of commit mode. This uses hidden methods of JSONLD-DATASET-PROXY // Take the LdoProxy out of commit mode. This uses hidden methods of JSONLD-DATASET-PROXY
const proxy = getProxyFromObject(input); const proxy = getProxyFromObject(input);
proxy[_proxyContext] = proxy[_proxyContext].duplicate({ proxy[_proxyContext] = proxy[_proxyContext].duplicate({
dataset: proxy[_proxyContext].state dataset: proxy[_proxyContext].state
.parentDataset as SubscribableDataset<Quad>, .parentDataset as SubscribableDataset<Quad>,
}); });
const dataset = getDataset(input) as SolidLdoDataset; return result;
return dataset.commitChangesToPod(changes as DatasetChanges<Quad>);
} }

@ -1,4 +1,4 @@
import type { BulkEditableDataset } from "@ldo/subscribable-dataset"; import type { IBulkEditableDataset } from "@ldo/subscribable-dataset";
import type { Quad } from "@rdfjs/types"; import type { Quad } from "@rdfjs/types";
/** /**
@ -18,5 +18,5 @@ export interface DatasetRequestOptions extends BasicRequestOptions {
/** /**
* A dataset to be modified with any new information obtained from a request * A dataset to be modified with any new information obtained from a request
*/ */
dataset?: BulkEditableDataset<Quad>; dataset?: IBulkEditableDataset<Quad>;
} }

@ -0,0 +1,14 @@
import type { ResourceGetterOptions } from "./ResourceStore";
import type { Container } from "./resource/Container";
import type { Leaf } from "./resource/Leaf";
import type { ContainerUri, LeafUri } from "./util/uriTypes";
/**
* A SolidLdoDataset provides methods for getting Solid resources.
*/
export interface ISolidLdoDataset {
getResource(uri: ContainerUri, options?: ResourceGetterOptions): Container;
getResource(uri: LeafUri, options?: ResourceGetterOptions): Leaf;
getResource(uri: string, options?: ResourceGetterOptions): Leaf | Container;
getResource(uri: string, options?: ResourceGetterOptions): Leaf | Container;
}

@ -10,6 +10,7 @@ import type {
import { changeData, commitData, createSolidLdoDataset } from "../src"; import { changeData, commitData, createSolidLdoDataset } from "../src";
import { import {
ROOT_CONTAINER, ROOT_CONTAINER,
WEB_ID,
createApp, createApp,
getAuthenticatedFetch, getAuthenticatedFetch,
} from "./solidServer.helper"; } from "./solidServer.helper";
@ -20,9 +21,6 @@ import {
defaultGraph, defaultGraph,
} from "@rdfjs/data-model"; } from "@rdfjs/data-model";
import type { CreateSuccess } from "../src/requester/results/success/CreateSuccess"; import type { CreateSuccess } from "../src/requester/results/success/CreateSuccess";
import type { DatasetChanges } from "@ldo/rdf-utils";
import { createDataset } from "@ldo/dataset";
import type { Quad } from "@rdfjs/types";
import type { AggregateSuccess } from "../src/requester/results/success/SuccessResult"; import type { AggregateSuccess } from "../src/requester/results/success/SuccessResult";
import type { import type {
UpdateDefaultGraphSuccess, UpdateDefaultGraphSuccess,
@ -91,6 +89,12 @@ const TEST_CONTAINER_TTL = `@prefix dc: <http://purl.org/dc/terms/>.
posix:size 522. posix:size 522.
<sample.txt> posix:mtime 1697810234; <sample.txt> posix:mtime 1697810234;
posix:size 10.`; posix:size 10.`;
const TEST_CONTAINER_ACL_URI = `${TEST_CONTAINER_URI}.acl`;
const TEST_CONTAINER_ACL = `<#b30e3fd1-b5a8-4763-ad9d-e95de9cf7933> a <http://www.w3.org/ns/auth/acl#Authorization>;
<http://www.w3.org/ns/auth/acl#accessTo> <${TEST_CONTAINER_URI}>;
<http://www.w3.org/ns/auth/acl#default> <${TEST_CONTAINER_URI}>;
<http://www.w3.org/ns/auth/acl#mode> <http://www.w3.org/ns/auth/acl#Read>, <http://www.w3.org/ns/auth/acl#Write>, <http://www.w3.org/ns/auth/acl#Append>, <http://www.w3.org/ns/auth/acl#Control>;
<http://www.w3.org/ns/auth/acl#agent> <${WEB_ID}>.`;
async function testRequestLoads<ReturnVal>( async function testRequestLoads<ReturnVal>(
request: () => Promise<ReturnVal>, request: () => Promise<ReturnVal>,
@ -166,6 +170,13 @@ describe("Integration", () => {
slug: TEST_CONTAINER_SLUG, slug: TEST_CONTAINER_SLUG,
}, },
}); });
await authFetch(TEST_CONTAINER_ACL_URI, {
method: "PUT",
headers: {
"content-type": "text/turtle",
},
body: TEST_CONTAINER_ACL,
});
await Promise.all([ await Promise.all([
authFetch(TEST_CONTAINER_URI, { authFetch(TEST_CONTAINER_URI, {
method: "POST", method: "POST",
@ -330,8 +341,8 @@ describe("Integration", () => {
expect(result.isError).toBe(true); expect(result.isError).toBe(true);
if (!result.isError) return; if (!result.isError) return;
expect(result.type).toBe("noncompliantPodError"); expect(result.type).toBe("noncompliantPodError");
expect(result.message).toBe( expect(result.message).toMatch(
"Response from https://solidweb.me/jackson3/test_ldo/sample2.ttl is not compliant with the Solid Specification: Resource requests must return a content-type header.", /\Response from .* is not compliant with the Solid Specification: Resource requests must return a content-type header\./,
); );
}); });
@ -351,8 +362,8 @@ describe("Integration", () => {
expect(result.isError).toBe(true); expect(result.isError).toBe(true);
if (!result.isError) return; if (!result.isError) return;
expect(result.type).toBe("noncompliantPodError"); expect(result.type).toBe("noncompliantPodError");
expect(result.message).toBe( expect(result.message).toMatch(
'Response from https://solidweb.me/jackson3/test_ldo/sample2.ttl is not compliant with the Solid Specification: Request returned noncompliant turtle: Unexpected "Error" on line 1.', /\Response from .* is not compliant with the Solid Specification: Request returned noncompliant turtle: Unexpected "Error" on line 1\./,
); );
}); });
@ -386,8 +397,8 @@ describe("Integration", () => {
expect(result.isError).toBe(true); expect(result.isError).toBe(true);
if (!result.isError) return; if (!result.isError) return;
expect(result.type).toBe("noncompliantPodError"); expect(result.type).toBe("noncompliantPodError");
expect(result.message).toBe( expect(result.message).toMatch(
"Response from https://solidweb.me/jackson3/test_ldo/ is not compliant with the Solid Specification: No link header present in request.", /\Response from .* is not compliant with the Solid Specification: No link header present in request\./,
); );
}); });
@ -406,26 +417,22 @@ describe("Integration", () => {
resource.read(), resource.read(),
]); ]);
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(result.type).toBe("dataReadSuccess"); expect(result.type).toBe("dataReadSuccess");
expect(result1.type).toBe("dataReadSuccess"); expect(result1.type).toBe("dataReadSuccess");
expect(fetchMock).toHaveBeenCalledTimes(1);
}); });
it("batches the read request when a read request is in queue", async () => { it("batches the read request when a read request is in queue", async () => {
const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI);
const [, result, result1] = await Promise.all([ const [, result, result1] = await Promise.all([
resource.update({ resource.createAndOverwrite(),
added: createDataset([
createQuad(namedNode("a"), namedNode("b"), namedNode("c")),
]),
}),
resource.read(), resource.read(),
resource.read(), resource.read(),
]); ]);
expect(fetchMock).toHaveBeenCalledTimes(3);
expect(result.type).toBe("dataReadSuccess"); expect(result.type).toBe("dataReadSuccess");
expect(result1.type).toBe("dataReadSuccess"); expect(result1.type).toBe("dataReadSuccess");
expect(fetchMock).toHaveBeenCalledTimes(2);
}); });
}); });
@ -546,8 +553,8 @@ describe("Integration", () => {
expect(result.isError).toBe(true); expect(result.isError).toBe(true);
if (!result.isError) return; if (!result.isError) return;
expect(result.type).toBe("noncompliantPodError"); expect(result.type).toBe("noncompliantPodError");
expect(result.message).toBe( expect(result.message).toMatch(
"Response from https://solidweb.me/jackson3/test_ldo/ is not compliant with the Solid Specification: No link header present in request.", /\Response from .* is not compliant with the Solid Specification: No link header present in request\./,
); );
}); });
@ -983,28 +990,28 @@ describe("Integration", () => {
* Update * Update
*/ */
describe("updateDataResource", () => { describe("updateDataResource", () => {
const changes: DatasetChanges<Quad> = { const normanQuad = createQuad(
added: createDataset([ namedNode("http://example.org/#green-goblin"),
createQuad( namedNode("http://xmlns.com/foaf/0.1/name"),
namedNode("http://example.org/#green-goblin"), literal("Norman Osborn"),
namedNode("http://xmlns.com/foaf/0.1/name"), namedNode(SAMPLE_DATA_URI),
literal("Norman Osborn"), );
namedNode(SAMPLE_DATA_URI),
), const goblinQuad = createQuad(
]), namedNode("http://example.org/#green-goblin"),
removed: createDataset([ namedNode("http://xmlns.com/foaf/0.1/name"),
createQuad( literal("Green Goblin"),
namedNode("http://example.org/#green-goblin"), namedNode(SAMPLE_DATA_URI),
namedNode("http://xmlns.com/foaf/0.1/name"), );
literal("Green Goblin"),
namedNode(SAMPLE_DATA_URI),
),
]),
};
it("applies changes to a Pod", async () => { it("applies changes to a Pod", async () => {
const result = await testRequestLoads( const result = await testRequestLoads(
() => solidLdoDataset.commitChangesToPod(changes), () => {
const transaction = solidLdoDataset.startTransaction();
transaction.add(normanQuad);
transaction.delete(goblinQuad);
return transaction.commitToPod();
},
solidLdoDataset.getResource(SAMPLE_DATA_URI), solidLdoDataset.getResource(SAMPLE_DATA_URI),
{ {
isLoading: true, isLoading: true,
@ -1017,41 +1024,17 @@ describe("Integration", () => {
>; >;
expect(aggregateSuccess.results.length).toBe(1); expect(aggregateSuccess.results.length).toBe(1);
expect(aggregateSuccess.results[0].type === "updateSuccess").toBe(true); expect(aggregateSuccess.results[0].type === "updateSuccess").toBe(true);
expect( expect(solidLdoDataset.has(normanQuad)).toBe(true);
solidLdoDataset.has( expect(solidLdoDataset.has(goblinQuad)).toBe(false);
createQuad(
namedNode("http://example.org/#green-goblin"),
namedNode("http://xmlns.com/foaf/0.1/name"),
literal("Norman Osborn"),
namedNode(SAMPLE_DATA_URI),
),
),
).toBe(true);
expect(
solidLdoDataset.has(
createQuad(
namedNode("http://example.org/#green-goblin"),
namedNode("http://xmlns.com/foaf/0.1/name"),
literal("Green Goblin"),
namedNode(SAMPLE_DATA_URI),
),
),
).toBe(false);
}); });
it("applies only remove changes to the Pod", async () => { it("applies only remove changes to the Pod", async () => {
const changes: DatasetChanges<Quad> = {
removed: createDataset([
createQuad(
namedNode("http://example.org/#green-goblin"),
namedNode("http://xmlns.com/foaf/0.1/name"),
literal("Green Goblin"),
namedNode(SAMPLE_DATA_URI),
),
]),
};
const result = await testRequestLoads( const result = await testRequestLoads(
() => solidLdoDataset.commitChangesToPod(changes), () => {
const transaction = solidLdoDataset.startTransaction();
transaction.delete(goblinQuad);
return transaction.commitToPod();
},
solidLdoDataset.getResource(SAMPLE_DATA_URI), solidLdoDataset.getResource(SAMPLE_DATA_URI),
{ {
isLoading: true, isLoading: true,
@ -1064,21 +1047,17 @@ describe("Integration", () => {
>; >;
expect(aggregateSuccess.results.length).toBe(1); expect(aggregateSuccess.results.length).toBe(1);
expect(aggregateSuccess.results[0].type === "updateSuccess").toBe(true); expect(aggregateSuccess.results[0].type === "updateSuccess").toBe(true);
expect( expect(solidLdoDataset.has(goblinQuad)).toBe(false);
solidLdoDataset.has(
createQuad(
namedNode("http://example.org/#green-goblin"),
namedNode("http://xmlns.com/foaf/0.1/name"),
literal("Green Goblin"),
namedNode(SAMPLE_DATA_URI),
),
),
).toBe(false);
}); });
it("handles an HTTP error", async () => { it("handles an HTTP error", async () => {
fetchMock.mockResolvedValueOnce(new Response("Error", { status: 500 })); fetchMock.mockResolvedValueOnce(new Response("Error", { status: 500 }));
const result = await solidLdoDataset.commitChangesToPod(changes);
const transaction = solidLdoDataset.startTransaction();
transaction.add(normanQuad);
transaction.delete(goblinQuad);
const result = await transaction.commitToPod();
expect(result.isError).toBe(true); expect(result.isError).toBe(true);
expect(result.type).toBe("aggregateError"); expect(result.type).toBe("aggregateError");
const aggregateError = result as AggregateError< const aggregateError = result as AggregateError<
@ -1092,7 +1071,10 @@ describe("Integration", () => {
fetchMock.mockImplementationOnce(() => { fetchMock.mockImplementationOnce(() => {
throw new Error("Some Error"); throw new Error("Some Error");
}); });
const result = await solidLdoDataset.commitChangesToPod(changes); const transaction = solidLdoDataset.startTransaction();
transaction.add(normanQuad);
transaction.delete(goblinQuad);
const result = await transaction.commitToPod();
expect(result.isError).toBe(true); expect(result.isError).toBe(true);
expect(result.type).toBe("aggregateError"); expect(result.type).toBe("aggregateError");
const aggregateError = result as AggregateError< const aggregateError = result as AggregateError<
@ -1103,17 +1085,15 @@ describe("Integration", () => {
}); });
it("errors when trying to update a container", async () => { it("errors when trying to update a container", async () => {
const changes: DatasetChanges<Quad> = { const badContainerQuad = createQuad(
added: createDataset([ namedNode("http://example.org/#green-goblin"),
createQuad( namedNode("http://xmlns.com/foaf/0.1/name"),
namedNode("http://example.org/#green-goblin"), literal("Norman Osborn"),
namedNode("http://xmlns.com/foaf/0.1/name"), namedNode(SAMPLE_CONTAINER_URI),
literal("Norman Osborn"), );
namedNode(SAMPLE_CONTAINER_URI), const transaction = solidLdoDataset.startTransaction();
), transaction.add(badContainerQuad);
]), const result = await transaction.commitToPod();
};
const result = await solidLdoDataset.commitChangesToPod(changes);
expect(result.isError).toBe(true); expect(result.isError).toBe(true);
expect(result.type).toBe("aggregateError"); expect(result.type).toBe("aggregateError");
const aggregateError = result as AggregateError< const aggregateError = result as AggregateError<
@ -1124,17 +1104,15 @@ describe("Integration", () => {
}); });
it("writes to the default graph without fetching", async () => { it("writes to the default graph without fetching", async () => {
const changes: DatasetChanges<Quad> = { const defaultGraphQuad = createQuad(
added: createDataset([ namedNode("http://example.org/#green-goblin"),
createQuad( namedNode("http://xmlns.com/foaf/0.1/name"),
namedNode("http://example.org/#green-goblin"), literal("Norman Osborn"),
namedNode("http://xmlns.com/foaf/0.1/name"), defaultGraph(),
literal("Norman Osborn"), );
defaultGraph(), const transaction = solidLdoDataset.startTransaction();
), transaction.add(defaultGraphQuad);
]), const result = await transaction.commitToPod();
};
const result = await solidLdoDataset.commitChangesToPod(changes);
expect(result.type).toBe("aggregateSuccess"); expect(result.type).toBe("aggregateSuccess");
const aggregateSuccess = result as AggregateSuccess< const aggregateSuccess = result as AggregateSuccess<
ResourceSuccess<UpdateSuccess | UpdateDefaultGraphSuccess, Leaf> ResourceSuccess<UpdateSuccess | UpdateDefaultGraphSuccess, Leaf>
@ -1158,10 +1136,15 @@ describe("Integration", () => {
it("batches data update changes", async () => { it("batches data update changes", async () => {
const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI);
const transaction1 = solidLdoDataset.startTransaction();
transaction1.delete(goblinQuad);
const transaction2 = solidLdoDataset.startTransaction();
transaction2.add(normanQuad);
const [, updateResult1, updateResult2] = await Promise.all([ const [, updateResult1, updateResult2] = await Promise.all([
resource.read(), resource.read(),
solidLdoDataset.commitChangesToPod({ removed: changes.removed }), transaction1.commitToPod(),
solidLdoDataset.commitChangesToPod({ added: changes.added }), transaction2.commitToPod(),
]); ]);
expect(updateResult1.type).toBe("aggregateSuccess"); expect(updateResult1.type).toBe("aggregateSuccess");
expect(updateResult2.type).toBe("aggregateSuccess"); expect(updateResult2.type).toBe("aggregateSuccess");
@ -1445,10 +1428,8 @@ describe("Integration", () => {
* =========================================================================== * ===========================================================================
*/ */
describe("methods", () => { describe("methods", () => {
it("creates a data object for a specific subject", () => { it("creates a data object for a specific subject", async () => {
const resource = solidLdoDataset.getResource( const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI);
"https://example.com/resource.ttl",
);
const post = solidLdoDataset.createData( const post = solidLdoDataset.createData(
PostShShapeType, PostShShapeType,
"https://example.com/subject", "https://example.com/subject",
@ -1456,29 +1437,46 @@ describe("Integration", () => {
); );
post.type = { "@id": "CreativeWork" }; post.type = { "@id": "CreativeWork" };
expect(post.type["@id"]).toBe("CreativeWork"); expect(post.type["@id"]).toBe("CreativeWork");
commitData(post); const result = await commitData(post);
expect(result.type).toBe("aggregateSuccess");
expect( expect(
solidLdoDataset.has( solidLdoDataset.has(
createQuad( createQuad(
namedNode("https://example.com/subject"), namedNode("https://example.com/subject"),
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://schema.org/CreativeWork"), namedNode("http://schema.org/CreativeWork"),
namedNode("https://example.com/resource.ttl"), namedNode(SAMPLE_DATA_URI),
), ),
), ),
).toBe(true); ).toBe(true);
}); });
it("uses changeData to start a transaction", () => { it("handles an error when committing data", async () => {
const resource = solidLdoDataset.getResource( fetchMock.mockResolvedValueOnce(
"https://example.com/resource.ttl", new Response(SAMPLE_DATA_URI, {
status: 500,
}),
); );
const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI);
const post = solidLdoDataset.createData(
PostShShapeType,
"https://example.com/subject",
resource,
);
post.type = { "@id": "CreativeWork" };
expect(post.type["@id"]).toBe("CreativeWork");
const result = await commitData(post);
expect(result.isError).toBe(true);
});
it("uses changeData to start a transaction", async () => {
const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI);
solidLdoDataset.add( solidLdoDataset.add(
createQuad( createQuad(
namedNode("https://example.com/subject"), namedNode("https://example.com/subject"),
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://schema.org/CreativeWork"), namedNode("http://schema.org/CreativeWork"),
namedNode("https://example.com/resource.ttl"), namedNode(SAMPLE_DATA_URI),
), ),
); );
const post = solidLdoDataset const post = solidLdoDataset
@ -1487,14 +1485,15 @@ describe("Integration", () => {
const cPost = changeData(post, resource); const cPost = changeData(post, resource);
cPost.type = { "@id": "SocialMediaPosting" }; cPost.type = { "@id": "SocialMediaPosting" };
expect(cPost.type["@id"]).toBe("SocialMediaPosting"); expect(cPost.type["@id"]).toBe("SocialMediaPosting");
commitData(cPost); const result = await commitData(cPost);
expect(result.isError).toBe(false);
expect( expect(
solidLdoDataset.has( solidLdoDataset.has(
createQuad( createQuad(
namedNode("https://example.com/subject"), namedNode("https://example.com/subject"),
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://schema.org/SocialMediaPosting"), namedNode("http://schema.org/SocialMediaPosting"),
namedNode("https://example.com/resource.ttl"), namedNode(SAMPLE_DATA_URI),
), ),
), ),
).toBe(true); ).toBe(true);

@ -21,8 +21,10 @@ const config = [
]; ];
export const SERVER_DOMAIN = process.env.SERVER || "http://localhost:3001/"; export const SERVER_DOMAIN = process.env.SERVER || "http://localhost:3001/";
export const ROOT_ROUTE = process.env.ROOT_CONTAINER || "example/"; export const ROOT_ROUTE = process.env.ROOT_CONTAINER || "";
export const ROOT_CONTAINER = `${SERVER_DOMAIN}${ROOT_ROUTE}`; export const ROOT_CONTAINER = `${SERVER_DOMAIN}${ROOT_ROUTE}`;
export const WEB_ID =
process.env.WEB_ID || `${SERVER_DOMAIN}example/profile/card#me`;
// Use an increased timeout, since the CSS server takes too much setup time. // Use an increased timeout, since the CSS server takes too much setup time.
jest.setTimeout(40_000); jest.setTimeout(40_000);

@ -1,6 +1,6 @@
{ {
"name": "@ldo/subscribable-dataset", "name": "@ldo/subscribable-dataset",
"version": "0.0.1-alpha.18", "version": "0.0.1-alpha.19",
"description": "An RDFJS dataset implementation that can be subscribed to for updates", "description": "An RDFJS dataset implementation that can be subscribed to for updates",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {

@ -8,40 +8,36 @@ import type {
ObjectNode, ObjectNode,
GraphNode, GraphNode,
} from "@ldo/rdf-utils"; } from "@ldo/rdf-utils";
import type { import type { Dataset, BaseQuad, Term, DatasetFactory } from "@rdfjs/types";
Dataset,
BaseQuad,
Stream,
Term,
DatasetFactory,
} from "@rdfjs/types";
import type { import type {
nodeEventListener, nodeEventListener,
SubscribableDataset, ISubscribableDataset,
TransactionalDataset, ITransactionDataset,
ITransactionDatasetFactory,
} from "./types"; } from "./types";
import { ProxyTransactionalDataset } from "./ProxyTransactionalDataset"; import { ExtendedDataset } from "@ldo/dataset";
/** /**
* A wrapper for a dataset that allows subscriptions to be made on nodes to * A wrapper for a dataset that allows subscriptions to be made on nodes to
* be triggered whenever a quad containing that added or removed. * be triggered whenever a quad containing that added or removed.
*/ */
export class WrapperSubscribableDataset< export class SubscribableDataset<InAndOutQuad extends BaseQuad = BaseQuad>
InAndOutQuad extends BaseQuad = BaseQuad, extends ExtendedDataset<InAndOutQuad>
> implements SubscribableDataset<InAndOutQuad> implements ISubscribableDataset<InAndOutQuad>
{ {
/** /**
* The underlying dataset factory * DatasetFactory for creating new datasets
*/ */
private datasetFactory: DatasetFactory<InAndOutQuad, InAndOutQuad>; protected datasetFactory: DatasetFactory<InAndOutQuad>;
/** /**
* The underlying dataset * The underlying event emitter
*/ */
private dataset: Dataset<InAndOutQuad, InAndOutQuad>; protected eventEmitter: EventEmitter;
/** /**
* The underlying event emitter * The underlying dataset factory for creating transaction datasets
*/ */
private eventEmitter: EventEmitter; protected transactionDatasetFactory: ITransactionDatasetFactory<InAndOutQuad>;
/** /**
* Helps find all the events for a given listener * Helps find all the events for a given listener
*/ */
@ -54,11 +50,13 @@ export class WrapperSubscribableDataset<
*/ */
constructor( constructor(
datasetFactory: DatasetFactory<InAndOutQuad, InAndOutQuad>, datasetFactory: DatasetFactory<InAndOutQuad, InAndOutQuad>,
transactionDatasetFactory: ITransactionDatasetFactory<InAndOutQuad>,
initialDataset?: Dataset<InAndOutQuad, InAndOutQuad>, initialDataset?: Dataset<InAndOutQuad, InAndOutQuad>,
) { ) {
this.datasetFactory = datasetFactory; super(initialDataset || datasetFactory.dataset(), datasetFactory);
this.dataset = initialDataset || this.datasetFactory.dataset(); this.transactionDatasetFactory = transactionDatasetFactory;
this.eventEmitter = new EventEmitter(); this.eventEmitter = new EventEmitter();
this.datasetFactory = datasetFactory;
} }
/** /**
@ -67,6 +65,18 @@ export class WrapperSubscribableDataset<
* ================================================================== * ==================================================================
*/ */
/**
* A helper method that mimics what the super of addAll would be
*/
private superAddAll(
quads: Dataset<InAndOutQuad, InAndOutQuad> | InAndOutQuad[],
): this {
for (const quad of quads) {
super.add(quad);
}
return this;
}
/** /**
* Imports the quads into this dataset. * 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. * 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.
@ -76,7 +86,7 @@ export class WrapperSubscribableDataset<
public addAll( public addAll(
quads: Dataset<InAndOutQuad, InAndOutQuad> | InAndOutQuad[], quads: Dataset<InAndOutQuad, InAndOutQuad> | InAndOutQuad[],
): this { ): this {
this.dataset.addAll(quads); this.superAddAll(quads);
this.triggerSubscriptionForQuads({ this.triggerSubscriptionForQuads({
added: this.datasetFactory.dataset(quads), added: this.datasetFactory.dataset(quads),
}); });
@ -89,26 +99,17 @@ export class WrapperSubscribableDataset<
*/ */
public bulk(changed: DatasetChanges<InAndOutQuad>): this { public bulk(changed: DatasetChanges<InAndOutQuad>): this {
if (changed.added) { if (changed.added) {
this.dataset.addAll(changed.added); this.superAddAll(changed.added);
} }
if (changed.removed) { if (changed.removed) {
changed.removed.forEach((quad) => { changed.removed.forEach((quad) => {
this.dataset.delete(quad); super.delete(quad);
}); });
} }
this.triggerSubscriptionForQuads(changed); this.triggerSubscriptionForQuads(changed);
return this; 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. * 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 subject
@ -123,191 +124,14 @@ export class WrapperSubscribableDataset<
object?: Term, object?: Term,
graph?: Term, graph?: Term,
): this { ): this {
const matching = this.dataset.match(subject, predicate, object, graph); const matching = super.match(subject, predicate, object, graph);
for (const quad of matching) { for (const quad of matching) {
this.dataset.delete(quad); super.delete(quad);
} }
this.triggerSubscriptionForQuads({ removed: matching }); this.triggerSubscriptionForQuads({ removed: matching });
return this; 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. * Adds the specified quad to the dataset.
* Existing quads, as defined in Quad.equals, will be ignored. * Existing quads, as defined in Quad.equals, will be ignored.
@ -315,7 +139,7 @@ export class WrapperSubscribableDataset<
* @returns the dataset instance it was called on. * @returns the dataset instance it was called on.
*/ */
public add(quad: InAndOutQuad): this { public add(quad: InAndOutQuad): this {
this.dataset.add(quad); super.add(quad);
this.triggerSubscriptionForQuads({ this.triggerSubscriptionForQuads({
added: this.datasetFactory.dataset([quad]), added: this.datasetFactory.dataset([quad]),
}); });
@ -328,28 +152,13 @@ export class WrapperSubscribableDataset<
* @param quad * @param quad
*/ */
public delete(quad: InAndOutQuad): this { public delete(quad: InAndOutQuad): this {
this.dataset.delete(quad); super.delete(quad);
this.triggerSubscriptionForQuads({ this.triggerSubscriptionForQuads({
removed: this.datasetFactory.dataset([quad]), removed: this.datasetFactory.dataset([quad]),
}); });
return this; 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 * EVENTEMITTER METHODS
@ -609,7 +418,7 @@ export class WrapperSubscribableDataset<
/** /**
* Returns a transactional dataset that will update this dataset when its transaction is committed. * Returns a transactional dataset that will update this dataset when its transaction is committed.
*/ */
public startTransaction(): TransactionalDataset<InAndOutQuad> { public startTransaction(): ITransactionDataset<InAndOutQuad> {
return new ProxyTransactionalDataset(this, this.datasetFactory); return this.transactionDatasetFactory.transactionDataset(this);
} }
} }

@ -0,0 +1,31 @@
import type { DatasetFactory, BaseQuad, Dataset } from "@rdfjs/types";
import type { ITransactionDatasetFactory } from "./types";
import { SubscribableDataset } from "./SubscribableDataset";
/**
* A DatasetFactory that returns a SubscribableDataset given a generic DatasetFactory.
*/
export class SubscribableDatasetFactory<
InAndOutQuad extends BaseQuad = BaseQuad,
> implements DatasetFactory<InAndOutQuad, InAndOutQuad>
{
protected datasetFactory: DatasetFactory<InAndOutQuad, InAndOutQuad>;
protected transactionDatasetFactory: ITransactionDatasetFactory<InAndOutQuad>;
constructor(
datasetFactory: DatasetFactory<InAndOutQuad, InAndOutQuad>,
transactionDatasetFactory: ITransactionDatasetFactory<InAndOutQuad>,
) {
this.datasetFactory = datasetFactory;
this.transactionDatasetFactory = transactionDatasetFactory;
}
dataset(
quads?: Dataset<InAndOutQuad, InAndOutQuad> | InAndOutQuad[],
): SubscribableDataset<InAndOutQuad> {
return new SubscribableDataset(
this.datasetFactory,
this.transactionDatasetFactory,
quads ? this.datasetFactory.dataset(quads) : undefined,
);
}
}

@ -1,27 +1,23 @@
import type { Dataset, BaseQuad, Term, DatasetFactory } from "@rdfjs/types"; import type { Dataset, BaseQuad, Term, DatasetFactory } from "@rdfjs/types";
import type { DatasetChanges } from "@ldo/rdf-utils"; import type { DatasetChanges } from "@ldo/rdf-utils";
import type { BulkEditableDataset, TransactionalDataset } from "./types"; import type { ITransactionDataset, ITransactionDatasetFactory } from "./types";
import { ExtendedDataset } from "@ldo/dataset";
import { mergeDatasetChanges } from "./mergeDatasetChanges"; import { mergeDatasetChanges } from "./mergeDatasetChanges";
import { SubscribableDataset } from "./SubscribableDataset";
import { updateDatasetInBulk } from "./util";
/** /**
* Proxy Transactional Dataset is a transactional dataset that does not duplicate * Proxy Transactional Dataset is a transactional dataset that does not duplicate
* the parent dataset, it will dynamically determine the correct return value for * the parent dataset, it will dynamically determine the correct return value for
* methods in real time when the method is called. * methods in real time when the method is called.
*/ */
export class ProxyTransactionalDataset<InAndOutQuad extends BaseQuad = BaseQuad> export class TransactionDataset<InAndOutQuad extends BaseQuad = BaseQuad>
extends ExtendedDataset<InAndOutQuad> extends SubscribableDataset<InAndOutQuad>
implements TransactionalDataset<InAndOutQuad> implements ITransactionDataset<InAndOutQuad>
{ {
/** /**
* The parent dataset that will be updated upon commit * The parent dataset that will be updated upon commit
*/ */
private parentDataset: Dataset<InAndOutQuad, InAndOutQuad>; public readonly 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 * The changes made that are ready to commit
@ -44,10 +40,10 @@ export class ProxyTransactionalDataset<InAndOutQuad extends BaseQuad = BaseQuad>
constructor( constructor(
parentDataset: Dataset<InAndOutQuad, InAndOutQuad>, parentDataset: Dataset<InAndOutQuad, InAndOutQuad>,
datasetFactory: DatasetFactory<InAndOutQuad, InAndOutQuad>, datasetFactory: DatasetFactory<InAndOutQuad, InAndOutQuad>,
transactionDatasetFactory: ITransactionDatasetFactory<InAndOutQuad>,
) { ) {
super(datasetFactory.dataset(), datasetFactory); super(datasetFactory, transactionDatasetFactory, datasetFactory.dataset());
this.parentDataset = parentDataset; this.parentDataset = parentDataset;
this.datasetFactory = datasetFactory;
this.datasetChanges = {}; this.datasetChanges = {};
} }
@ -251,21 +247,7 @@ export class ProxyTransactionalDataset<InAndOutQuad extends BaseQuad = BaseQuad>
* Helper method to update the parent dataset or any other provided dataset * Helper method to update the parent dataset or any other provided dataset
*/ */
private updateParentDataset(datasetChanges: DatasetChanges<InAndOutQuad>) { private updateParentDataset(datasetChanges: DatasetChanges<InAndOutQuad>) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any return updateDatasetInBulk(this.parentDataset, datasetChanges);
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);
});
}
}
} }
/** /**
@ -296,17 +278,6 @@ export class ProxyTransactionalDataset<InAndOutQuad extends BaseQuad = BaseQuad>
this.committedDatasetChanges = undefined; 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> { public getChanges(): DatasetChanges<InAndOutQuad> {
return this.datasetChanges; return this.datasetChanges;
} }

@ -0,0 +1,27 @@
import type { BaseQuad, DatasetFactory } from "@rdfjs/types";
import type { ISubscribableDataset, ITransactionDatasetFactory } from "./types";
import { TransactionDataset } from "./TransactionDataset";
export class TransactionDatasetFactory<InAndOutQuad extends BaseQuad>
implements ITransactionDatasetFactory<InAndOutQuad>
{
private datasetFactory: DatasetFactory<InAndOutQuad, InAndOutQuad>;
private transactionDatasetFactory: ITransactionDatasetFactory<InAndOutQuad>;
constructor(
datasetFactory: DatasetFactory<InAndOutQuad, InAndOutQuad>,
transactionDatasetFactory?: ITransactionDatasetFactory<InAndOutQuad>,
) {
this.datasetFactory = datasetFactory;
this.transactionDatasetFactory = transactionDatasetFactory || this;
}
transactionDataset(
parentDataset: ISubscribableDataset<InAndOutQuad>,
): TransactionDataset<InAndOutQuad> {
return new TransactionDataset<InAndOutQuad>(
parentDataset,
this.datasetFactory,
this.transactionDatasetFactory,
);
}
}

@ -1,27 +0,0 @@
import type { DatasetFactory, BaseQuad, Dataset } from "@rdfjs/types";
import { WrapperSubscribableDataset } from "./WrapperSubscribableDataset";
/**
* A DatasetFactory that returns a WrapperSubscribableDataset given a generic DatasetFactory.
*/
export 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,46 @@
import type { Dataset, DatasetFactory, Quad } from "@rdfjs/types";
import { createDataset } from "@ldo/dataset";
import { SubscribableDatasetFactory } from "./SubscribableDatasetFactory";
import type {
ISubscribableDataset,
ISubscribableDatasetFactory,
ITransactionDatasetFactory,
} from "./types";
import { TransactionDatasetFactory } from "./TransactionDatasetFactory";
const datasetFactory: DatasetFactory<Quad> = {
dataset: (quads?: Dataset<Quad> | Quad[]): Dataset<Quad> => {
return createDataset(quads);
},
};
/**
* Creates a factory that generates TransactionDatasets
* @returns TransactionDatasetFactory
*/
export function createTransactionDatasetFactory(): ITransactionDatasetFactory<Quad> {
return new TransactionDatasetFactory(datasetFactory);
}
/**
* Creates a dataset factory that generates a SubscribableDataset
* @returns DatasetFactory for SubscribableDataset
*/
export function createSubscribableDatasetFactory(): ISubscribableDatasetFactory<Quad> {
return new SubscribableDatasetFactory(
datasetFactory,
createTransactionDatasetFactory(),
);
}
/**
* Creates a SubscribableDataset
* @param quads: A dataset or array of Quads to initialize the dataset.
* @returns Dataset
*/
export function createSubscribableDataset(
quads?: Dataset<Quad> | Quad[],
): ISubscribableDataset<Quad> {
const subscribableDatasetFactory = createSubscribableDatasetFactory();
return subscribableDatasetFactory.dataset(quads);
}

@ -1,8 +1,8 @@
import type { Quad } from "@rdfjs/types"; import type { Quad } from "@rdfjs/types";
import type { ParserOptions } from "@ldo/rdf-utils"; import type { ParserOptions } from "@ldo/rdf-utils";
import { createDatasetFromSerializedInput } from "@ldo/dataset"; import { createDatasetFromSerializedInput } from "@ldo/dataset";
import { createWrapperSubscribableDatasetFactory } from "./createWrapperSubscribableDataset"; import { createSubscribableDatasetFactory } from "./createSubscribableDataset";
import type { WrapperSubscribableDataset } from "./WrapperSubscribableDataset"; import type { ISubscribableDataset } from "./types";
/** /**
* Creates a SubscribableDataset with a string input that could be JSON-LD, Turtle, N-Triples, TriG, RDF*, or N3. * Creates a SubscribableDataset with a string input that could be JSON-LD, Turtle, N-Triples, TriG, RDF*, or N3.
@ -18,9 +18,9 @@ import type { WrapperSubscribableDataset } from "./WrapperSubscribableDataset";
export async function createWrapperSubscribableDatasetFromSerializedInput( export async function createWrapperSubscribableDatasetFromSerializedInput(
data: string, data: string,
options?: ParserOptions, options?: ParserOptions,
): Promise<WrapperSubscribableDataset<Quad>> { ): Promise<ISubscribableDataset<Quad>> {
const datasetFactory = createWrapperSubscribableDatasetFactory(); const datasetFactory = createSubscribableDatasetFactory();
return createDatasetFromSerializedInput<WrapperSubscribableDataset<Quad>>( return createDatasetFromSerializedInput<ISubscribableDataset<Quad>>(
datasetFactory, datasetFactory,
data, data,
options, options,

@ -1,30 +0,0 @@
import type { Dataset, DatasetFactory, Quad } from "@rdfjs/types";
import type { WrapperSubscribableDataset } from "./WrapperSubscribableDataset";
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 function createWrapperSubscribableDataset(
quads?: Dataset<Quad> | Quad[],
): WrapperSubscribableDataset<Quad> {
const wrapperSubscribableDatasetFactory =
createWrapperSubscribableDatasetFactory();
return wrapperSubscribableDatasetFactory.dataset(quads);
}

@ -1,10 +1,9 @@
export { export * from "./createSubscribableDataset";
createWrapperSubscribableDataset as createSubscribableDataset, export { createWrapperSubscribableDatasetFromSerializedInput as serializedToSubscribableDataset } from "./createSubscribableDatasetFromSerializedInput";
createWrapperSubscribableDatasetFactory as createSubscribableDatasetFactory, export * from "./TransactionDataset";
} from "./createWrapperSubscribableDataset"; export * from "./TransactionDatasetFactory";
export { createWrapperSubscribableDatasetFromSerializedInput as serializedToSubscribableDataset } from "./createWrapperSubscribableDatasetFromSerializedInput"; export * from "./SubscribableDataset";
export * from "./ProxyTransactionalDataset"; export * from "./SubscribableDatasetFactory";
export * from "./WrapperSubscribableDataset";
export * from "./WrapperSubscribableDatasetFactory";
export * from "./types"; export * from "./types";
export * from "./mergeDatasetChanges"; export * from "./mergeDatasetChanges";
export * from "./util";

@ -1,5 +1,5 @@
import type { DatasetChanges, QuadMatch } from "@ldo/rdf-utils"; import type { DatasetChanges, QuadMatch } from "@ldo/rdf-utils";
import type { Dataset, BaseQuad } from "@rdfjs/types"; import type { Dataset, BaseQuad, DatasetFactory } from "@rdfjs/types";
/** /**
* An event listeners for nodes * An event listeners for nodes
@ -11,28 +11,29 @@ export type nodeEventListener<InAndOutQuad extends BaseQuad = BaseQuad> = (
/** /**
* Adds the bulk method for add and remove * Adds the bulk method for add and remove
*/ */
export interface BulkEditableDataset<InAndOutQuad extends BaseQuad = BaseQuad> export interface IBulkEditableDataset<InAndOutQuad extends BaseQuad = BaseQuad>
extends Dataset<InAndOutQuad, InAndOutQuad> { extends Dataset<InAndOutQuad, InAndOutQuad> {
bulk(changes: DatasetChanges<InAndOutQuad>): this; bulk(changes: DatasetChanges<InAndOutQuad>): this;
} }
/** /**
* A dataset that allows you to modify the dataset and * Factory for creating SubscribableDatasets
*/ */
export interface TransactionalDataset<InAndOutQuad extends BaseQuad = BaseQuad> export type ISubscribableDatasetFactory<
extends BulkEditableDataset<InAndOutQuad> { InAndOutQuad extends BaseQuad = BaseQuad,
rollback(): void; > = DatasetFactory<
commit(): void; InAndOutQuad,
getChanges(): DatasetChanges<InAndOutQuad>; InAndOutQuad,
} ISubscribableDataset<InAndOutQuad>
>;
/** /**
* Dataset that allows developers to subscribe to a sepecific term and be alerted * 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 * if a quad is added or removed containing that term. It's methods follow the
* EventEmitter interface except take in namedNodes as keys. * EventEmitter interface except take in namedNodes as keys.
*/ */
export interface SubscribableDataset<InAndOutQuad extends BaseQuad = BaseQuad> export interface ISubscribableDataset<InAndOutQuad extends BaseQuad = BaseQuad>
extends BulkEditableDataset<InAndOutQuad> { extends IBulkEditableDataset<InAndOutQuad> {
/** /**
* Alias for emitter.on(eventName, listener). * Alias for emitter.on(eventName, listener).
* @param eventName * @param eventName
@ -140,5 +141,27 @@ export interface SubscribableDataset<InAndOutQuad extends BaseQuad = BaseQuad>
/** /**
* Returns a transactional dataset that will update this dataset when its transaction is committed. * Returns a transactional dataset that will update this dataset when its transaction is committed.
*/ */
startTransaction(): TransactionalDataset<InAndOutQuad>; startTransaction(): ITransactionDataset<InAndOutQuad>;
}
/**
* Creates a TransactionDataset
*/
export interface ITransactionDatasetFactory<
InAndOutQuad extends BaseQuad = BaseQuad,
> {
transactionDataset(
parent: Dataset<InAndOutQuad, InAndOutQuad>,
): ITransactionDataset<InAndOutQuad>;
}
/**
* A dataset that allows you to modify the dataset and
*/
export interface ITransactionDataset<InAndOutQuad extends BaseQuad = BaseQuad>
extends ISubscribableDataset<InAndOutQuad> {
readonly parentDataset: Dataset<InAndOutQuad, InAndOutQuad>;
rollback(): void;
commit(): void;
getChanges(): DatasetChanges<InAndOutQuad>;
} }

@ -0,0 +1,27 @@
import type { DatasetChanges } from "@ldo/rdf-utils";
import type { BaseQuad, Dataset } from "@rdfjs/types";
import type { IBulkEditableDataset } from "./types";
/**
* Performs a bulk update for a dataset even if it doesn't have a bulk method.
* @param dataset - the input dataset
* @param datasetChanges - changes to be applied
*/
export function updateDatasetInBulk<InAndOutQuad extends BaseQuad = BaseQuad>(
dataset: Dataset<InAndOutQuad>,
datasetChanges: DatasetChanges<InAndOutQuad>,
) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((dataset as any).bulk) {
(dataset as IBulkEditableDataset<InAndOutQuad>).bulk(datasetChanges);
} else {
if (datasetChanges.added) {
dataset.addAll(datasetChanges.added);
}
if (datasetChanges.removed) {
datasetChanges.removed.forEach((curQuad) => {
dataset.delete(curQuad);
});
}
}
}

@ -1,5 +1,5 @@
import type { SubscribableDataset } from "../src"; import type { ISubscribableDataset } from "../src";
import { ProxyTransactionalDataset, createSubscribableDataset } from "../src"; import { TransactionDataset, createSubscribableDataset } from "../src";
import { createDataset } from "@ldo/dataset"; import { createDataset } from "@ldo/dataset";
import { import {
namedNode, namedNode,
@ -11,14 +11,14 @@ import {
import type { Quad, BlankNode } from "@rdfjs/types"; import type { Quad, BlankNode } from "@rdfjs/types";
import testDataset from "@ldo/dataset/test/dataset.testHelper"; import testDataset from "@ldo/dataset/test/dataset.testHelper";
describe("WrapperSubscribableDataset", () => { describe("SubscribableDataset", () => {
// Regular dataset tests // Regular dataset tests
testDataset({ testDataset({
dataset: createSubscribableDataset, dataset: createSubscribableDataset,
}); });
// Subscribable Dataset tests // Subscribable Dataset tests
let subscribableDatastet: SubscribableDataset<Quad>; let subscribableDatastet: ISubscribableDataset<Quad>;
const tomTypeQuad = quad( const tomTypeQuad = quad(
namedNode("http://example.org/cartoons#Tom"), namedNode("http://example.org/cartoons#Tom"),
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
@ -449,8 +449,7 @@ describe("WrapperSubscribableDataset", () => {
it("Returns a transaction", () => { it("Returns a transaction", () => {
expect( expect(
subscribableDatastet.startTransaction() instanceof subscribableDatastet.startTransaction() instanceof TransactionDataset,
ProxyTransactionalDataset,
).toBe(true); ).toBe(true);
}); });
}); });

@ -5,14 +5,18 @@ import type {
Quad, Quad,
DatasetCore, DatasetCore,
} from "@rdfjs/types"; } from "@rdfjs/types";
import type { BulkEditableDataset } from "../src"; import type { ISubscribableDataset } from "../src";
import { ExtendedDatasetFactory } from "@ldo/dataset"; import { ExtendedDatasetFactory, createDataset } from "@ldo/dataset";
import { ProxyTransactionalDataset } from "../src"; import {
TransactionDataset,
createSubscribableDataset,
createTransactionDatasetFactory,
} from "../src";
import datasetCoreFactory from "@rdfjs/dataset"; import datasetCoreFactory from "@rdfjs/dataset";
describe("ProxyTransactionalDataset", () => { describe("TransactionDataset", () => {
let parentDataset: Dataset<Quad>; let parentDataset: ISubscribableDataset<Quad>;
let transactionalDataset: ProxyTransactionalDataset<Quad>; let transactionalDataset: TransactionDataset<Quad>;
const tomTypeQuad = quad( const tomTypeQuad = quad(
namedNode("http://example.org/cartoons#Tom"), namedNode("http://example.org/cartoons#Tom"),
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
@ -41,12 +45,13 @@ describe("ProxyTransactionalDataset", () => {
const extendedDatasetFactory = new ExtendedDatasetFactory(datasetFactory); const extendedDatasetFactory = new ExtendedDatasetFactory(datasetFactory);
const initializeWithExtendedDatasetParent = (quads?: Quad[]) => { const initializeWithExtendedDatasetParent = (quads?: Quad[]) => {
parentDataset = extendedDatasetFactory.dataset( parentDataset = createSubscribableDataset(
quads || [tomTypeQuad, tomNameQuad], quads || [tomTypeQuad, tomNameQuad],
); );
transactionalDataset = new ProxyTransactionalDataset( transactionalDataset = new TransactionDataset(
parentDataset, parentDataset,
extendedDatasetFactory, extendedDatasetFactory,
createTransactionDatasetFactory(),
); );
}; };
@ -301,14 +306,15 @@ describe("ProxyTransactionalDataset", () => {
// Disable for tests // Disable for tests
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
const mockParent: BulkEditableDataset<Quad> = { const mockParent: ISubscribableDataset<Quad> = {
bulk: jest.fn(), bulk: jest.fn(),
has: (curQuad) => parentDataset.has(curQuad), has: (curQuad) => parentDataset.has(curQuad),
[Symbol.iterator]: () => parentDataset[Symbol.iterator](), [Symbol.iterator]: () => parentDataset[Symbol.iterator](),
}; };
transactionalDataset = new ProxyTransactionalDataset<Quad>( transactionalDataset = new TransactionDataset<Quad>(
mockParent, mockParent,
extendedDatasetFactory, extendedDatasetFactory,
createTransactionDatasetFactory(),
); );
transactionalDataset.add(lickyNameQuad); transactionalDataset.add(lickyNameQuad);
@ -317,10 +323,27 @@ describe("ProxyTransactionalDataset", () => {
expect(mockParent.bulk).toHaveBeenCalled(); expect(mockParent.bulk).toHaveBeenCalled();
}); });
it("Uses bulk update on commit when the parent dataset is not bulk updatable", () => {
// Disable for tests
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const mockParent: Dataset<Quad> = createDataset([tomTypeQuad]);
transactionalDataset = new TransactionDataset<Quad>(
mockParent,
extendedDatasetFactory,
createTransactionDatasetFactory(),
);
transactionalDataset.add(lickyNameQuad);
transactionalDataset.delete(tomTypeQuad);
transactionalDataset.commit();
expect(mockParent.has(lickyNameQuad)).toBe(true);
expect(mockParent.has(tomTypeQuad)).toBe(false);
});
it("Returns a transactional dataset", () => { it("Returns a transactional dataset", () => {
expect( expect(
transactionalDataset.startTransaction() instanceof transactionalDataset.startTransaction() instanceof TransactionDataset,
ProxyTransactionalDataset,
).toBe(true); ).toBe(true);
}); });

@ -2,17 +2,19 @@ import {
createSubscribableDataset, createSubscribableDataset,
createSubscribableDatasetFactory, createSubscribableDatasetFactory,
serializedToSubscribableDataset, serializedToSubscribableDataset,
ProxyTransactionalDataset, SubscribableDataset,
WrapperSubscribableDataset, SubscribableDatasetFactory,
WrapperSubscribableDatasetFactory, TransactionDataset,
TransactionDatasetFactory,
} from "../src"; } from "../src";
describe("Exports", () => { describe("Exports", () => {
it("Has all exports", () => { it("Has all exports", () => {
expect(createSubscribableDataset); expect(createSubscribableDataset);
expect(ProxyTransactionalDataset); expect(SubscribableDataset);
expect(WrapperSubscribableDataset); expect(TransactionDataset);
expect(WrapperSubscribableDatasetFactory); expect(SubscribableDatasetFactory);
expect(TransactionDatasetFactory);
expect(serializedToSubscribableDataset); expect(serializedToSubscribableDataset);
expect(createSubscribableDatasetFactory); expect(createSubscribableDatasetFactory);
}); });

Loading…
Cancel
Save