javascript-solid-server 0.0.19 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/handlers/resource.js +35 -30
- package/test/patch.test.js +8 -3
- package/test/sparql-update.test.js +8 -3
- package/test-data-idp-accounts/.idp/accounts/00f5d7c4-1da9-4e68-92c9-2b931f7c5750.json +9 -0
- package/test-data-idp-accounts/.idp/accounts/_email_index.json +1 -1
- package/test-data-idp-accounts/.idp/accounts/_webid_index.json +1 -1
- package/test-data-idp-accounts/.idp/keys/jwks.json +8 -8
- package/test-data-idp-accounts/.idp/accounts/95d4d470-1e8c-4b17-bcff-a62b8a54813b.json +0 -9
package/package.json
CHANGED
package/src/handlers/resource.js
CHANGED
|
@@ -451,40 +451,44 @@ export async function handlePatch(request, reply) {
|
|
|
451
451
|
});
|
|
452
452
|
}
|
|
453
453
|
|
|
454
|
-
// Check if resource exists
|
|
454
|
+
// Check if resource exists - PATCH can create resources in Solid
|
|
455
455
|
const stats = await storage.stat(storagePath);
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
+
}
|
|
470
466
|
}
|
|
471
467
|
}
|
|
472
468
|
|
|
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
|
|
480
470
|
let document;
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
+
};
|
|
488
492
|
}
|
|
489
493
|
|
|
490
494
|
// Parse the patch
|
|
@@ -553,5 +557,6 @@ export async function handlePatch(request, reply) {
|
|
|
553
557
|
emitChange(resourceUrl);
|
|
554
558
|
}
|
|
555
559
|
|
|
556
|
-
|
|
560
|
+
// Return 201 Created if resource was created, 204 No Content if updated
|
|
561
|
+
return reply.code(resourceExists ? 204 : 201).send();
|
|
557
562
|
}
|
package/test/patch.test.js
CHANGED
|
@@ -207,21 +207,26 @@ describe('PATCH Operations', () => {
|
|
|
207
207
|
assertStatus(res, 415);
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
-
it('should
|
|
210
|
+
it('should create resource if it does not exist', async () => {
|
|
211
211
|
const patch = `
|
|
212
212
|
@prefix solid: <http://www.w3.org/ns/solid/terms#>.
|
|
213
213
|
_:patch a solid:InsertDeletePatch;
|
|
214
214
|
solid:inserts { <#me> <http://example.org/p> "test" }.
|
|
215
215
|
`;
|
|
216
216
|
|
|
217
|
-
const res = await request('/patchtest/public/
|
|
217
|
+
const res = await request('/patchtest/public/patch-created.json', {
|
|
218
218
|
method: 'PATCH',
|
|
219
219
|
headers: { 'Content-Type': 'text/n3' },
|
|
220
220
|
body: patch,
|
|
221
221
|
auth: 'patchtest'
|
|
222
222
|
});
|
|
223
223
|
|
|
224
|
-
|
|
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);
|
|
225
230
|
});
|
|
226
231
|
|
|
227
232
|
it('should return 409 when patching non-JSON-LD resource', async () => {
|
|
@@ -157,15 +157,20 @@ describe('SPARQL Update', () => {
|
|
|
157
157
|
});
|
|
158
158
|
|
|
159
159
|
describe('Error handling', () => {
|
|
160
|
-
it('should
|
|
160
|
+
it('should create resource if it does not exist', async () => {
|
|
161
161
|
const sparql = `INSERT DATA { <#x> <http://example.org/p> "v" }`;
|
|
162
|
-
const res = await request('/sparqltest/public/
|
|
162
|
+
const res = await request('/sparqltest/public/sparql-created.json', {
|
|
163
163
|
method: 'PATCH',
|
|
164
164
|
headers: { 'Content-Type': 'application/sparql-update' },
|
|
165
165
|
body: sparql,
|
|
166
166
|
auth: 'sparqltest'
|
|
167
167
|
});
|
|
168
|
-
|
|
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);
|
|
169
174
|
});
|
|
170
175
|
|
|
171
176
|
it('should return 415 for unsupported content type', async () => {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "00f5d7c4-1da9-4e68-92c9-2b931f7c5750",
|
|
3
|
+
"email": "credtest@example.com",
|
|
4
|
+
"passwordHash": "$2b$10$oH0fl4a/riqSc4oXZ1pmLuSQOP9AlRSSrOMg7OlP5O2m.C39mOTL6",
|
|
5
|
+
"webId": "http://localhost:3101/credtest/#me",
|
|
6
|
+
"podName": "credtest",
|
|
7
|
+
"createdAt": "2025-12-27T14:04:29.630Z",
|
|
8
|
+
"lastLogin": "2025-12-27T14:04:29.865Z"
|
|
9
|
+
}
|
|
@@ -3,20 +3,20 @@
|
|
|
3
3
|
"keys": [
|
|
4
4
|
{
|
|
5
5
|
"kty": "EC",
|
|
6
|
-
"x": "
|
|
7
|
-
"y": "
|
|
6
|
+
"x": "qUZf3Zu9iULTVgQu_ARo02vLb7MedW9t3jzW6EykbAc",
|
|
7
|
+
"y": "f7fkZpYdSy-m_0vhcw1l5wh4nnHmWLtuw9DEfZNqMyI",
|
|
8
8
|
"crv": "P-256",
|
|
9
|
-
"d": "
|
|
10
|
-
"kid": "
|
|
9
|
+
"d": "711_8EPt8sDLyzDB174cNsAGvkIdZLMZKPlH-kFZ270",
|
|
10
|
+
"kid": "af8ee1cc-110c-4e6e-bd2a-12fd7690c20c",
|
|
11
11
|
"use": "sig",
|
|
12
12
|
"alg": "ES256",
|
|
13
|
-
"iat":
|
|
13
|
+
"iat": 1766844269
|
|
14
14
|
}
|
|
15
15
|
]
|
|
16
16
|
},
|
|
17
17
|
"cookieKeys": [
|
|
18
|
-
"
|
|
19
|
-
"
|
|
18
|
+
"vgN9MVzjMn5ehD1LoDQVnDO_kkTdmCmQbOFhjxwJMnE",
|
|
19
|
+
"x8blYyo4zMs0Vm8sXy1XFDeKPIMD34yPw1_LO1vv5z4"
|
|
20
20
|
],
|
|
21
|
-
"createdAt": "2025-12-
|
|
21
|
+
"createdAt": "2025-12-27T14:04:29.573Z"
|
|
22
22
|
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "95d4d470-1e8c-4b17-bcff-a62b8a54813b",
|
|
3
|
-
"email": "credtest@example.com",
|
|
4
|
-
"passwordHash": "$2b$10$dfsBq19GkIsL/qaer5LlxeSD8cMFJS4U8o7Wt06f/u29Cdhc8eJ3i",
|
|
5
|
-
"webId": "http://localhost:3101/credtest/#me",
|
|
6
|
-
"podName": "credtest",
|
|
7
|
-
"createdAt": "2025-12-27T13:57:26.941Z",
|
|
8
|
-
"lastLogin": "2025-12-27T13:57:27.182Z"
|
|
9
|
-
}
|