Skip to content

Commit 9e381f5

Browse files
Add SPARQL Update support for Turtle files in PATCH handler
1 parent 9cfc17e commit 9e381f5

3 files changed

Lines changed: 80 additions & 4 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "javascript-solid-server",
3-
"version": "0.0.29",
3+
"version": "0.0.30",
44
"description": "A minimal, fast Solid server",
55
"main": "src/index.js",
66
"type": "module",

src/handlers/resource.js

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,10 +533,86 @@ export async function handlePatch(request, reply) {
533533
});
534534
}
535535
} else {
536-
// Parse as plain JSON-LD
536+
// Try to parse as JSON-LD first
537537
try {
538538
document = JSON.parse(contentStr);
539539
} catch (e) {
540+
// Not JSON - might be Turtle, handle with RDF store for SPARQL Update
541+
if (isSparqlUpdate) {
542+
// Parse Turtle and apply SPARQL Update directly
543+
const { Parser, Writer } = await import('n3');
544+
const parser = new Parser({ baseIRI: resourceUrl });
545+
let quads;
546+
try {
547+
quads = parser.parse(contentStr);
548+
} catch (parseErr) {
549+
return reply.code(409).send({
550+
error: 'Conflict',
551+
message: 'Resource is not valid Turtle: ' + parseErr.message
552+
});
553+
}
554+
555+
// Parse the SPARQL Update
556+
const patchContent = Buffer.isBuffer(request.body) ? request.body.toString() : request.body;
557+
let update;
558+
try {
559+
update = parseSparqlUpdate(patchContent, resourceUrl);
560+
} catch (parseErr) {
561+
return reply.code(400).send({
562+
error: 'Bad Request',
563+
message: 'Invalid SPARQL Update: ' + parseErr.message
564+
});
565+
}
566+
567+
// Apply deletes
568+
for (const triple of update.deletes) {
569+
quads = quads.filter(q => {
570+
const matches = q.subject.value === triple.subject &&
571+
q.predicate.value === triple.predicate &&
572+
(q.object.value === (triple.object['@id'] || triple.object['@value'] || triple.object));
573+
return !matches;
574+
});
575+
}
576+
577+
// Apply inserts
578+
const { DataFactory } = await import('n3');
579+
const { namedNode, literal } = DataFactory;
580+
for (const triple of update.inserts) {
581+
const subj = namedNode(triple.subject);
582+
const pred = namedNode(triple.predicate);
583+
let obj;
584+
if (triple.object['@id']) {
585+
obj = namedNode(triple.object['@id']);
586+
} else if (typeof triple.object === 'string') {
587+
obj = literal(triple.object);
588+
} else {
589+
obj = literal(triple.object['@value'] || triple.object);
590+
}
591+
quads.push(DataFactory.quad(subj, pred, obj));
592+
}
593+
594+
// Serialize back to Turtle
595+
const writer = new Writer({ prefixes: {} });
596+
quads.forEach(q => writer.addQuad(q));
597+
let turtleOutput;
598+
writer.end((err, result) => { turtleOutput = result; });
599+
600+
const success = await storage.write(storagePath, Buffer.from(turtleOutput));
601+
if (!success) {
602+
return reply.code(500).send({ error: 'Write failed' });
603+
}
604+
605+
const origin = request.headers.origin;
606+
const headers = getAllHeaders({ isContainer: false, origin, resourceUrl });
607+
Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
608+
609+
if (request.notificationsEnabled) {
610+
emitChange(resourceUrl);
611+
}
612+
613+
return reply.code(resourceExists ? 204 : 201).send();
614+
}
615+
540616
return reply.code(409).send({
541617
error: 'Conflict',
542618
message: 'Resource is not valid JSON-LD and cannot be patched'

0 commit comments

Comments
 (0)