parent
00b1c6ef6f
commit
8eceab9f3d
@ -0,0 +1,31 @@ |
|||||||
|
import { ContextDefinition } from "jsonld"; |
||||||
|
|
||||||
|
/** |
||||||
|
* ============================================================================= |
||||||
|
* postContext: JSONLD Context for post |
||||||
|
* ============================================================================= |
||||||
|
*/ |
||||||
|
export const postContext: ContextDefinition = { |
||||||
|
type: { |
||||||
|
"@id": "@type", |
||||||
|
}, |
||||||
|
SocialMediaPosting: "http://schema.org/SocialMediaPosting", |
||||||
|
CreativeWork: "http://schema.org/CreativeWork", |
||||||
|
Thing: "http://schema.org/Thing", |
||||||
|
articleBody: { |
||||||
|
"@id": "http://schema.org/articleBody", |
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||||
|
}, |
||||||
|
uploadDate: { |
||||||
|
"@id": "http://schema.org/uploadDate", |
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#date", |
||||||
|
}, |
||||||
|
image: { |
||||||
|
"@id": "http://schema.org/image", |
||||||
|
"@type": "@id", |
||||||
|
}, |
||||||
|
publisher: { |
||||||
|
"@id": "http://schema.org/publisher", |
||||||
|
"@type": "@id", |
||||||
|
}, |
||||||
|
}; |
@ -0,0 +1,155 @@ |
|||||||
|
import { Schema } from "shexj"; |
||||||
|
|
||||||
|
/** |
||||||
|
* ============================================================================= |
||||||
|
* postSchema: ShexJ Schema for post |
||||||
|
* ============================================================================= |
||||||
|
*/ |
||||||
|
export const postSchema: Schema = { |
||||||
|
type: "Schema", |
||||||
|
shapes: [ |
||||||
|
{ |
||||||
|
id: "https://example.com/PostSh", |
||||||
|
type: "ShapeDecl", |
||||||
|
shapeExpr: { |
||||||
|
type: "Shape", |
||||||
|
expression: { |
||||||
|
type: "EachOf", |
||||||
|
expressions: [ |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
values: [ |
||||||
|
"http://schema.org/SocialMediaPosting", |
||||||
|
"http://schema.org/CreativeWork", |
||||||
|
"http://schema.org/Thing", |
||||||
|
], |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://schema.org/articleBody", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
datatype: "http://www.w3.org/2001/XMLSchema#string", |
||||||
|
}, |
||||||
|
min: 0, |
||||||
|
max: 1, |
||||||
|
annotations: [ |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#label", |
||||||
|
object: { |
||||||
|
value: "articleBody", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||||
|
object: { |
||||||
|
value: "The actual body of the article. ", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://schema.org/uploadDate", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
datatype: "http://www.w3.org/2001/XMLSchema#date", |
||||||
|
}, |
||||||
|
annotations: [ |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#label", |
||||||
|
object: { |
||||||
|
value: "uploadDate", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||||
|
object: { |
||||||
|
value: |
||||||
|
"Date when this media object was uploaded to this site.", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://schema.org/image", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
nodeKind: "iri", |
||||||
|
}, |
||||||
|
min: 0, |
||||||
|
max: 1, |
||||||
|
annotations: [ |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#label", |
||||||
|
object: { |
||||||
|
value: "image", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||||
|
object: { |
||||||
|
value: |
||||||
|
"A media object that encodes this CreativeWork. This property is a synonym for encoding.", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://schema.org/publisher", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
nodeKind: "iri", |
||||||
|
}, |
||||||
|
annotations: [ |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#label", |
||||||
|
object: { |
||||||
|
value: "publisher", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||||
|
object: { |
||||||
|
value: "The publisher of the creative work.", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
annotations: [ |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#label", |
||||||
|
object: { |
||||||
|
value: "SocialMediaPost", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||||
|
object: { |
||||||
|
value: |
||||||
|
"A post to a social media platform, including blog posts, tweets, Facebook posts, etc.", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}; |
@ -0,0 +1,19 @@ |
|||||||
|
import { ShapeType } from "@ldo/ldo"; |
||||||
|
import { postSchema } from "./post.schema"; |
||||||
|
import { postContext } from "./post.context"; |
||||||
|
import { PostSh } from "./post.typings"; |
||||||
|
|
||||||
|
/** |
||||||
|
* ============================================================================= |
||||||
|
* LDO ShapeTypes post |
||||||
|
* ============================================================================= |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* PostSh ShapeType |
||||||
|
*/ |
||||||
|
export const PostShShapeType: ShapeType<PostSh> = { |
||||||
|
schema: postSchema, |
||||||
|
shape: "https://example.com/PostSh", |
||||||
|
context: postContext, |
||||||
|
}; |
@ -0,0 +1,45 @@ |
|||||||
|
import { ContextDefinition } from "jsonld"; |
||||||
|
|
||||||
|
/** |
||||||
|
* ============================================================================= |
||||||
|
* Typescript Typings for post |
||||||
|
* ============================================================================= |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* PostSh Type |
||||||
|
*/ |
||||||
|
export interface PostSh { |
||||||
|
"@id"?: string; |
||||||
|
"@context"?: ContextDefinition; |
||||||
|
type: |
||||||
|
| { |
||||||
|
"@id": "SocialMediaPosting"; |
||||||
|
} |
||||||
|
| { |
||||||
|
"@id": "CreativeWork"; |
||||||
|
} |
||||||
|
| { |
||||||
|
"@id": "Thing"; |
||||||
|
}; |
||||||
|
/** |
||||||
|
* The actual body of the article. |
||||||
|
*/ |
||||||
|
articleBody?: string; |
||||||
|
/** |
||||||
|
* Date when this media object was uploaded to this site. |
||||||
|
*/ |
||||||
|
uploadDate: string; |
||||||
|
/** |
||||||
|
* A media object that encodes this CreativeWork. This property is a synonym for encoding. |
||||||
|
*/ |
||||||
|
image?: { |
||||||
|
"@id": string; |
||||||
|
}; |
||||||
|
/** |
||||||
|
* The publisher of the creative work. |
||||||
|
*/ |
||||||
|
publisher: { |
||||||
|
"@id": string; |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> |
||||||
|
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> |
||||||
|
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> |
||||||
|
PREFIX ex: <https://example.com/> |
||||||
|
BASE <http://schema.org/> |
||||||
|
|
||||||
|
ex:PostSh { |
||||||
|
a [<SocialMediaPosting> <CreativeWork> <Thing>] ; |
||||||
|
<articleBody> xsd:string? |
||||||
|
// rdfs:label '''articleBody''' |
||||||
|
// rdfs:comment '''The actual body of the article. ''' ; |
||||||
|
<uploadDate> xsd:date |
||||||
|
// rdfs:label '''uploadDate''' |
||||||
|
// rdfs:comment '''Date when this media object was uploaded to this site.''' ; |
||||||
|
<image> IRI ? |
||||||
|
// rdfs:label '''image''' |
||||||
|
// rdfs:comment '''A media object that encodes this CreativeWork. This property is a synonym for encoding.''' ; |
||||||
|
<publisher> IRI |
||||||
|
// rdfs:label '''publisher''' |
||||||
|
// rdfs:comment '''The publisher of the creative work.''' ; |
||||||
|
} |
||||||
|
// rdfs:label '''SocialMediaPost''' |
||||||
|
// rdfs:comment '''A post to a social media platform, including blog posts, tweets, Facebook posts, etc.''' |
@ -1,10 +1,91 @@ |
|||||||
import React, { useCallback } from "react"; |
import React, { useCallback, useState, useRef } from "react"; |
||||||
import type { FunctionComponent } from "react"; |
import type { FunctionComponent, FormEvent } from "react"; |
||||||
|
import type { Container, Leaf } from "@ldo/solid"; |
||||||
|
import { v4 } from "uuid"; |
||||||
|
import { useLdo, useSolidAuth } from "@ldo/solid-react"; |
||||||
|
import { PostShShapeType } from "../.ldo/post.shapeTypes"; |
||||||
|
|
||||||
export const UploadButton: FunctionComponent = () => { |
export const UploadButton: FunctionComponent<{ mainContainer: Container }> = ({ |
||||||
const upload = useCallback(() => { |
mainContainer, |
||||||
const _message = prompt("Type a message for your post"); |
}) => { |
||||||
}, []); |
const [message, setMessage] = useState(""); |
||||||
|
const [selectedFile, setSelectedFile] = useState<File | undefined>(); |
||||||
|
const fileInputRef = useRef<HTMLInputElement | null>(null); |
||||||
|
const { createData, commitData } = useLdo(); |
||||||
|
const { session } = useSolidAuth(); |
||||||
|
const onSubmit = useCallback( |
||||||
|
async (e: FormEvent<HTMLFormElement>) => { |
||||||
|
e.preventDefault(); |
||||||
|
|
||||||
return <button onClick={upload}>Make a New Post</button>; |
// Create the container file
|
||||||
|
const mediaContainer = await mainContainer.createChildAndOverwrite( |
||||||
|
`${v4()}/`, |
||||||
|
); |
||||||
|
if (mediaContainer.type === "error") { |
||||||
|
alert(mediaContainer.message); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Upload Image
|
||||||
|
let uploadedImage: Leaf | undefined; |
||||||
|
if (selectedFile) { |
||||||
|
const result = await mediaContainer.uploadChildAndOverwrite( |
||||||
|
selectedFile.name, |
||||||
|
selectedFile, |
||||||
|
selectedFile.type, |
||||||
|
); |
||||||
|
if (result.type === "error") { |
||||||
|
alert(result.message); |
||||||
|
await mediaContainer.delete(); |
||||||
|
return; |
||||||
|
} |
||||||
|
uploadedImage = result; |
||||||
|
} |
||||||
|
|
||||||
|
// Create Post
|
||||||
|
const indexResource = mediaContainer.child("index.ttl"); |
||||||
|
const post = createData( |
||||||
|
PostShShapeType, |
||||||
|
indexResource.uri, |
||||||
|
indexResource, |
||||||
|
); |
||||||
|
post.articleBody = message; |
||||||
|
if (uploadedImage) { |
||||||
|
post.image = { "@id": uploadedImage.uri }; |
||||||
|
} |
||||||
|
if (session.webId) { |
||||||
|
post.publisher = { "@id": session.webId }; |
||||||
|
} |
||||||
|
post.type = { "@id": "SocialMediaPosting" }; |
||||||
|
post.uploadDate = new Date().toISOString(); |
||||||
|
const result = await commitData(post); |
||||||
|
if (result.type === "error") { |
||||||
|
alert(result.message); |
||||||
|
} |
||||||
|
|
||||||
|
// Clear the UI after Upload
|
||||||
|
setMessage(""); |
||||||
|
setSelectedFile(undefined); |
||||||
|
if (fileInputRef.current) fileInputRef.current.value = ""; |
||||||
|
}, |
||||||
|
[message, selectedFile, session.webId], |
||||||
|
); |
||||||
|
|
||||||
|
return ( |
||||||
|
<form onSubmit={onSubmit}> |
||||||
|
<input |
||||||
|
type="text" |
||||||
|
placeholder="Make a Post" |
||||||
|
value={message} |
||||||
|
onChange={(e) => setMessage(e.target.value)} |
||||||
|
/> |
||||||
|
<input |
||||||
|
type="file" |
||||||
|
accept="image/*" |
||||||
|
ref={fileInputRef} |
||||||
|
onChange={(e) => setSelectedFile(e.target.files?.[0])} |
||||||
|
/> |
||||||
|
<input type="submit" value="Post" /> |
||||||
|
</form> |
||||||
|
); |
||||||
}; |
}; |
||||||
|
@ -0,0 +1,91 @@ |
|||||||
|
import type { LdoBase, ShapeType } from "@ldo/ldo"; |
||||||
|
import { transactionChanges } from "@ldo/ldo"; |
||||||
|
import { write } from "@ldo/ldo"; |
||||||
|
import { startTransaction } from "@ldo/ldo"; |
||||||
|
import type { SubjectNode } from "@ldo/rdf-utils"; |
||||||
|
import type { Resource, SolidLdoDataset } from "@ldo/solid"; |
||||||
|
|
||||||
|
export interface UseLdoMethods { |
||||||
|
dataset: SolidLdoDataset; |
||||||
|
getResource: SolidLdoDataset["getResource"]; |
||||||
|
getSubject<Type extends LdoBase>( |
||||||
|
shapeType: ShapeType<Type>, |
||||||
|
subject: string | SubjectNode, |
||||||
|
): Type | Error; |
||||||
|
createData<Type extends LdoBase>( |
||||||
|
shapeType: ShapeType<Type>, |
||||||
|
subject: string | SubjectNode, |
||||||
|
...resources: Resource[] |
||||||
|
): Type; |
||||||
|
changeData<Type extends LdoBase>(input: Type, ...resources: Resource[]): Type; |
||||||
|
commitData(input: LdoBase): ReturnType<SolidLdoDataset["commitChangesToPod"]>; |
||||||
|
} |
||||||
|
|
||||||
|
export function createUseLdoMethods(dataset: SolidLdoDataset): UseLdoMethods { |
||||||
|
return { |
||||||
|
dataset: dataset, |
||||||
|
/** |
||||||
|
* Gets a resource |
||||||
|
*/ |
||||||
|
getResource: dataset.getResource.bind(dataset), |
||||||
|
/** |
||||||
|
* Returns a Linked Data Object for a subject |
||||||
|
* @param shapeType The shape type for the data |
||||||
|
* @param subject Subject Node |
||||||
|
* @returns A Linked Data Object |
||||||
|
*/ |
||||||
|
getSubject<Type extends LdoBase>( |
||||||
|
shapeType: ShapeType<Type>, |
||||||
|
subject: string | SubjectNode, |
||||||
|
): Type | Error { |
||||||
|
return dataset.usingType(shapeType).fromSubject(subject); |
||||||
|
}, |
||||||
|
/** |
||||||
|
* Begins tracking changes to eventually commit for a new subject |
||||||
|
* @param shapeType The shape type that defines the created data |
||||||
|
* @param subject The RDF subject for a Linked Data Object |
||||||
|
* @param resources Any number of resources to which this data should be written |
||||||
|
* @returns A Linked Data Object to modify and commit |
||||||
|
*/ |
||||||
|
createData<Type extends LdoBase>( |
||||||
|
shapeType: ShapeType<Type>, |
||||||
|
subject: string | SubjectNode, |
||||||
|
...resources: Resource[] |
||||||
|
): Type { |
||||||
|
const linkedDataObject = dataset |
||||||
|
.usingType(shapeType) |
||||||
|
.write(...resources.map((r) => r.uri)) |
||||||
|
.fromSubject(subject); |
||||||
|
startTransaction(linkedDataObject); |
||||||
|
return linkedDataObject; |
||||||
|
}, |
||||||
|
/** |
||||||
|
* Begins tracking changes to eventually commit |
||||||
|
* @param input A linked data object to track changes on |
||||||
|
* @param resources |
||||||
|
*/ |
||||||
|
changeData<Type extends LdoBase>( |
||||||
|
input: Type, |
||||||
|
...resources: Resource[] |
||||||
|
): Type { |
||||||
|
// Clone the input and set a graph
|
||||||
|
const [transactionLdo] = write(...resources.map((r) => r.uri)).usingCopy( |
||||||
|
input, |
||||||
|
); |
||||||
|
// Start a transaction with the input
|
||||||
|
startTransaction(transactionLdo); |
||||||
|
// Return
|
||||||
|
return transactionLdo; |
||||||
|
}, |
||||||
|
/** |
||||||
|
* Commits the transaction to the global dataset, syncing all subscribing |
||||||
|
* components and Solid Pods |
||||||
|
*/ |
||||||
|
commitData( |
||||||
|
input: LdoBase, |
||||||
|
): ReturnType<SolidLdoDataset["commitChangesToPod"]> { |
||||||
|
const changes = transactionChanges(input); |
||||||
|
return dataset.commitChangesToPod(changes); |
||||||
|
}, |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
import type { SubjectNode } from "@ldo/rdf-utils"; |
||||||
|
import { |
||||||
|
ContextUtil, |
||||||
|
JsonldDatasetProxyBuilder, |
||||||
|
} from "@ldo/jsonld-dataset-proxy"; |
||||||
|
import type { ShapeType } from "@ldo/ldo"; |
||||||
|
import { LdoBuilder } from "@ldo/ldo"; |
||||||
|
import type { LdoBase } from "@ldo/ldo"; |
||||||
|
import { useLdo } from "./SolidLdoProvider"; |
||||||
|
import { useCallback, useEffect, useMemo, useState } from "react"; |
||||||
|
import { TrackingProxyContext } from "./util/TrackingProxyContext"; |
||||||
|
import { defaultGraph } from "@rdfjs/data-model"; |
||||||
|
|
||||||
|
export function useSubject<Type extends LdoBase>( |
||||||
|
shapeType: ShapeType<Type>, |
||||||
|
subject: string | SubjectNode, |
||||||
|
): Type { |
||||||
|
const { dataset } = useLdo(); |
||||||
|
|
||||||
|
const [forceUpdateCounter, setForceUpdateCounter] = useState(0); |
||||||
|
const forceUpdate = useCallback( |
||||||
|
() => setForceUpdateCounter((val) => val + 1), |
||||||
|
[], |
||||||
|
); |
||||||
|
|
||||||
|
// The main linked data object
|
||||||
|
const linkedDataObject = useMemo(() => { |
||||||
|
// Remove all current subscriptions
|
||||||
|
dataset.removeListenerFromAllEvents(forceUpdate); |
||||||
|
|
||||||
|
// Rebuild the LdoBuilder from scratch to inject TrackingProxyContext
|
||||||
|
const contextUtil = new ContextUtil(shapeType.context); |
||||||
|
const proxyContext = new TrackingProxyContext( |
||||||
|
{ |
||||||
|
dataset, |
||||||
|
contextUtil, |
||||||
|
writeGraphs: [defaultGraph()], |
||||||
|
languageOrdering: ["none", "en", "other"], |
||||||
|
}, |
||||||
|
forceUpdate, |
||||||
|
); |
||||||
|
const builder = new LdoBuilder( |
||||||
|
new JsonldDatasetProxyBuilder(proxyContext), |
||||||
|
shapeType, |
||||||
|
); |
||||||
|
return builder.fromSubject(subject); |
||||||
|
}, [shapeType, subject, dataset, forceUpdateCounter, forceUpdate]); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
// Unregister force update listener upon unmount
|
||||||
|
return () => { |
||||||
|
dataset.removeListenerFromAllEvents(forceUpdate); |
||||||
|
}; |
||||||
|
}, [shapeType, subject]); |
||||||
|
|
||||||
|
return linkedDataObject; |
||||||
|
} |
@ -1,3 +1,42 @@ |
|||||||
|
import type { DatasetChanges } from "@ldo/rdf-utils"; |
||||||
|
import { mergeDatasetChanges } from "@ldo/subscribable-dataset"; |
||||||
|
import type { Quad } from "@rdfjs/types"; |
||||||
import { Requester } from "./Requester"; |
import { Requester } from "./Requester"; |
||||||
|
import type { UpdateResult } from "./requests/updateDataResource"; |
||||||
|
import { updateDataResource } from "./requests/updateDataResource"; |
||||||
|
|
||||||
export class LeafRequester extends Requester {} |
export const UPDATE_KEY = "update"; |
||||||
|
|
||||||
|
export class LeafRequester extends Requester { |
||||||
|
isUpdating(): boolean { |
||||||
|
return this.requestBatcher.isLoading(UPDATE_KEY); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Update the data on this resource |
||||||
|
* @param changes |
||||||
|
*/ |
||||||
|
async updateDataResource( |
||||||
|
changes: DatasetChanges<Quad>, |
||||||
|
): Promise<UpdateResult> { |
||||||
|
const result = await this.requestBatcher.queueProcess({ |
||||||
|
name: UPDATE_KEY, |
||||||
|
args: [ |
||||||
|
{ uri: this.uri, fetch: this.context.fetch }, |
||||||
|
changes, |
||||||
|
this.context.solidLdoDataset, |
||||||
|
], |
||||||
|
perform: updateDataResource, |
||||||
|
modifyQueue: (queue, isLoading, [, changes]) => { |
||||||
|
if (queue[queue.length - 1].name === UPDATE_KEY) { |
||||||
|
// Merge Changes
|
||||||
|
const originalChanges = queue[queue.length - 1].args[1]; |
||||||
|
mergeDatasetChanges(originalChanges, changes); |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
}, |
||||||
|
}); |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
@ -0,0 +1,12 @@ |
|||||||
|
import type { Container } from "../../resource/Container"; |
||||||
|
import type { Leaf } from "../../resource/Leaf"; |
||||||
|
import { RequesterResult } from "./RequesterResult"; |
||||||
|
|
||||||
|
export class CommitChangesSuccess extends RequesterResult { |
||||||
|
readonly type = "commitChangesSuccess" as const; |
||||||
|
readonly affectedResources: (Leaf | Container)[]; |
||||||
|
constructor(uri: string, affectedResources: (Leaf | Container)[]) { |
||||||
|
super(uri); |
||||||
|
this.affectedResources = affectedResources; |
||||||
|
} |
||||||
|
} |
@ -1,15 +1,45 @@ |
|||||||
import type { DatasetChanges } from "@ldo/rdf-utils"; |
import type { DatasetChanges } from "@ldo/rdf-utils"; |
||||||
import type { DataResult } from "../requestResults/DataResult"; |
import { changesToSparqlUpdate } from "@ldo/rdf-utils"; |
||||||
|
import { DataResult } from "../requestResults/DataResult"; |
||||||
import type { HttpErrorResultType } from "../requestResults/HttpErrorResult"; |
import type { HttpErrorResultType } from "../requestResults/HttpErrorResult"; |
||||||
import type { UnexpectedError } from "../requestResults/ErrorResult"; |
import { HttpErrorResult } from "../requestResults/HttpErrorResult"; |
||||||
import type { RequestParams } from "./requestParams"; |
import { UnexpectedError } from "../requestResults/ErrorResult"; |
||||||
|
import type { SimpleRequestParams } from "./requestParams"; |
||||||
|
import type { SubscribableDataset } from "@ldo/subscribable-dataset"; |
||||||
|
import type { Quad } from "@rdfjs/types"; |
||||||
|
|
||||||
export type UpdateResult = DataResult | UpdateResultError; |
export type UpdateResult = DataResult | UpdateResultError; |
||||||
export type UpdateResultError = HttpErrorResultType | UnexpectedError; |
export type UpdateResultError = HttpErrorResultType | UnexpectedError; |
||||||
|
|
||||||
export async function updateDataResource( |
export async function updateDataResource( |
||||||
_params: RequestParams, |
{ uri, fetch }: SimpleRequestParams, |
||||||
_datasetChanges: DatasetChanges, |
datasetChanges: DatasetChanges<Quad>, |
||||||
|
mainDataset: SubscribableDataset<Quad>, |
||||||
): Promise<UpdateResult> { |
): Promise<UpdateResult> { |
||||||
throw new Error("Not Implemented"); |
try { |
||||||
|
// Put Changes in transactional dataset
|
||||||
|
const transaction = mainDataset.startTransaction(); |
||||||
|
transaction.addAll(datasetChanges.added || []); |
||||||
|
datasetChanges.removed?.forEach((quad) => transaction.delete(quad)); |
||||||
|
// Commit data optimistically
|
||||||
|
transaction.commit(); |
||||||
|
// Make request
|
||||||
|
const sparqlUpdate = await changesToSparqlUpdate(datasetChanges); |
||||||
|
const response = await fetch(uri, { |
||||||
|
method: "PATCH", |
||||||
|
body: sparqlUpdate, |
||||||
|
headers: { |
||||||
|
"Content-Type": "application/sparql-update", |
||||||
|
}, |
||||||
|
}); |
||||||
|
const httpError = HttpErrorResult.checkResponse(uri, response); |
||||||
|
if (httpError) { |
||||||
|
// Handle error rollback
|
||||||
|
transaction.rollback(); |
||||||
|
return httpError; |
||||||
|
} |
||||||
|
return new DataResult(uri); |
||||||
|
} catch (err) { |
||||||
|
return UnexpectedError.fromThrown(uri, err); |
||||||
|
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,53 @@ |
|||||||
|
import type { DatasetChanges } from "@ldo/rdf-utils"; |
||||||
|
import type { BaseQuad } from "@rdfjs/types"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Merges a new change into an original change |
||||||
|
* @param originalChange |
||||||
|
* @param newChange |
||||||
|
*/ |
||||||
|
export function mergeDatasetChanges<InAndOutQuad extends BaseQuad>( |
||||||
|
originalChange: DatasetChanges<InAndOutQuad>, |
||||||
|
newChange: DatasetChanges<InAndOutQuad>, |
||||||
|
): void { |
||||||
|
// Add added
|
||||||
|
if (newChange.added) { |
||||||
|
if (originalChange.added) { |
||||||
|
originalChange.added.addAll(newChange.added); |
||||||
|
} else { |
||||||
|
originalChange.added = newChange.added; |
||||||
|
} |
||||||
|
// Delete from removed if present
|
||||||
|
const changesIntersection = originalChange.removed?.intersection( |
||||||
|
newChange.added, |
||||||
|
); |
||||||
|
if (changesIntersection && changesIntersection.size > 0) { |
||||||
|
originalChange.removed = |
||||||
|
originalChange.removed?.difference(changesIntersection); |
||||||
|
} |
||||||
|
} |
||||||
|
// Add removed
|
||||||
|
if (newChange.removed) { |
||||||
|
if (originalChange.removed) { |
||||||
|
originalChange.removed.addAll(newChange.removed); |
||||||
|
} else { |
||||||
|
originalChange.removed = newChange.removed; |
||||||
|
} |
||||||
|
// Delete from added if present
|
||||||
|
const changesIntersection = originalChange.added?.intersection( |
||||||
|
newChange.removed, |
||||||
|
); |
||||||
|
if (changesIntersection && changesIntersection.size > 0) { |
||||||
|
originalChange.added = |
||||||
|
originalChange.added?.difference(changesIntersection); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Make undefined if size is zero
|
||||||
|
if (originalChange.added && originalChange.added.size === 0) { |
||||||
|
originalChange.added = undefined; |
||||||
|
} |
||||||
|
if (originalChange.removed && originalChange.removed.size === 0) { |
||||||
|
originalChange.removed = undefined; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue