Skip to content

Commit ae36b0c

Browse files
Bump to v0.0.20 - PATCH creates resources if they don't exist
Fixes #2 - PodOS compatibility for creating resources via PATCH - PATCH on non-existent resource creates it with empty JSON-LD base - Returns 201 Created for new resources, 204 No Content for updates - If-Match header only checked when resource already exists - Updated tests to expect resource creation behavior
1 parent 19ce90e commit ae36b0c

4 files changed

Lines changed: 52 additions & 37 deletions

File tree

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.19",
3+
"version": "0.0.20",
44
"description": "A minimal, fast Solid server",
55
"main": "src/index.js",
66
"type": "module",

src/handlers/resource.js

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -451,40 +451,44 @@ export async function handlePatch(request, reply) {
451451
});
452452
}
453453

454-
// Check if resource exists
454+
// Check if resource exists - PATCH can create resources in Solid
455455
const stats = await storage.stat(storagePath);
456-
if (!stats) {
457-
const origin = request.headers.origin;
458-
const connegEnabled = request.connegEnabled || false;
459-
const headers = getNotFoundHeaders({ resourceUrl, origin, connegEnabled });
460-
Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
461-
return reply.code(404).send({ error: 'Not Found' });
462-
}
463-
464-
// Check If-Match header (for safe updates)
465-
const ifMatch = request.headers['if-match'];
466-
if (ifMatch) {
467-
const check = checkIfMatch(ifMatch, stats.etag);
468-
if (!check.ok) {
469-
return reply.code(check.status).send({ error: check.error });
456+
const resourceExists = !!stats;
457+
458+
// Check If-Match header (for safe updates) - only if resource exists
459+
if (resourceExists) {
460+
const ifMatch = request.headers['if-match'];
461+
if (ifMatch) {
462+
const check = checkIfMatch(ifMatch, stats.etag);
463+
if (!check.ok) {
464+
return reply.code(check.status).send({ error: check.error });
465+
}
470466
}
471467
}
472468

473-
// Read existing content
474-
const existingContent = await storage.read(storagePath);
475-
if (existingContent === null) {
476-
return reply.code(500).send({ error: 'Read error' });
477-
}
478-
479-
// Parse existing document as JSON-LD
469+
// Read existing content or start with empty JSON-LD document
480470
let document;
481-
try {
482-
document = JSON.parse(existingContent.toString());
483-
} catch (e) {
484-
return reply.code(409).send({
485-
error: 'Conflict',
486-
message: 'Resource is not valid JSON-LD and cannot be patched'
487-
});
471+
if (resourceExists) {
472+
const existingContent = await storage.read(storagePath);
473+
if (existingContent === null) {
474+
return reply.code(500).send({ error: 'Read error' });
475+
}
476+
477+
// Parse existing document as JSON-LD
478+
try {
479+
document = JSON.parse(existingContent.toString());
480+
} catch (e) {
481+
return reply.code(409).send({
482+
error: 'Conflict',
483+
message: 'Resource is not valid JSON-LD and cannot be patched'
484+
});
485+
}
486+
} else {
487+
// Create empty JSON-LD document for new resource
488+
document = {
489+
'@context': {},
490+
'@graph': []
491+
};
488492
}
489493

490494
// Parse the patch
@@ -553,5 +557,6 @@ export async function handlePatch(request, reply) {
553557
emitChange(resourceUrl);
554558
}
555559

556-
return reply.code(204).send();
560+
// Return 201 Created if resource was created, 204 No Content if updated
561+
return reply.code(resourceExists ? 204 : 201).send();
557562
}

test/patch.test.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,21 +207,26 @@ describe('PATCH Operations', () => {
207207
assertStatus(res, 415);
208208
});
209209

210-
it('should return 404 for non-existent resource', async () => {
210+
it('should create resource if it does not exist', async () => {
211211
const patch = `
212212
@prefix solid: <http://www.w3.org/ns/solid/terms#>.
213213
_:patch a solid:InsertDeletePatch;
214214
solid:inserts { <#me> <http://example.org/p> "test" }.
215215
`;
216216

217-
const res = await request('/patchtest/public/nonexistent.json', {
217+
const res = await request('/patchtest/public/patch-created.json', {
218218
method: 'PATCH',
219219
headers: { 'Content-Type': 'text/n3' },
220220
body: patch,
221221
auth: 'patchtest'
222222
});
223223

224-
assertStatus(res, 404);
224+
// PATCH creates resources in Solid
225+
assertStatus(res, 201);
226+
227+
// Verify resource was created with the inserted data
228+
const getRes = await request('/patchtest/public/patch-created.json');
229+
assertStatus(getRes, 200);
225230
});
226231

227232
it('should return 409 when patching non-JSON-LD resource', async () => {

test/sparql-update.test.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,15 +157,20 @@ describe('SPARQL Update', () => {
157157
});
158158

159159
describe('Error handling', () => {
160-
it('should return 404 for non-existent resource', async () => {
160+
it('should create resource if it does not exist', async () => {
161161
const sparql = `INSERT DATA { <#x> <http://example.org/p> "v" }`;
162-
const res = await request('/sparqltest/public/nonexistent.json', {
162+
const res = await request('/sparqltest/public/sparql-created.json', {
163163
method: 'PATCH',
164164
headers: { 'Content-Type': 'application/sparql-update' },
165165
body: sparql,
166166
auth: 'sparqltest'
167167
});
168-
assertStatus(res, 404);
168+
// PATCH creates resources in Solid
169+
assertStatus(res, 201);
170+
171+
// Verify resource was created
172+
const getRes = await request('/sparqltest/public/sparql-created.json');
173+
assertStatus(getRes, 200);
169174
});
170175

171176
it('should return 415 for unsupported content type', async () => {

0 commit comments

Comments
 (0)