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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -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
- 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
+ }
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
- 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
+ };
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
- 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();
557
562
  }
@@ -207,21 +207,26 @@ describe('PATCH Operations', () => {
207
207
  assertStatus(res, 415);
208
208
  });
209
209
 
210
- it('should return 404 for non-existent resource', async () => {
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/nonexistent.json', {
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
- 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);
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 return 404 for non-existent resource', async () => {
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/nonexistent.json', {
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
- 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);
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
+ }
@@ -1,3 +1,3 @@
1
1
  {
2
- "credtest@example.com": "95d4d470-1e8c-4b17-bcff-a62b8a54813b"
2
+ "credtest@example.com": "00f5d7c4-1da9-4e68-92c9-2b931f7c5750"
3
3
  }
@@ -1,3 +1,3 @@
1
1
  {
2
- "http://localhost:3101/credtest/#me": "95d4d470-1e8c-4b17-bcff-a62b8a54813b"
2
+ "http://localhost:3101/credtest/#me": "00f5d7c4-1da9-4e68-92c9-2b931f7c5750"
3
3
  }
@@ -3,20 +3,20 @@
3
3
  "keys": [
4
4
  {
5
5
  "kty": "EC",
6
- "x": "_0WHR5nff4EnQiPYLZEuPKNmV7qWxizbdY6NeepX4gc",
7
- "y": "JowkolFdgQSs4GZ7nGN5ljMII8QAxHDYv2Qp4x5eHgo",
6
+ "x": "qUZf3Zu9iULTVgQu_ARo02vLb7MedW9t3jzW6EykbAc",
7
+ "y": "f7fkZpYdSy-m_0vhcw1l5wh4nnHmWLtuw9DEfZNqMyI",
8
8
  "crv": "P-256",
9
- "d": "zvFCMQvrEJwSTkfloHws3EFQRA0o5abR22so5LcCTao",
10
- "kid": "ed8976e4-f907-4fb4-82ff-846a62807dd7",
9
+ "d": "711_8EPt8sDLyzDB174cNsAGvkIdZLMZKPlH-kFZ270",
10
+ "kid": "af8ee1cc-110c-4e6e-bd2a-12fd7690c20c",
11
11
  "use": "sig",
12
12
  "alg": "ES256",
13
- "iat": 1766843846
13
+ "iat": 1766844269
14
14
  }
15
15
  ]
16
16
  },
17
17
  "cookieKeys": [
18
- "oQ-IdseWVLpOiwPmpYeY1ShFHI3lLHRiW6NsYeNBpP0",
19
- "xrKoXpxC4O7wOZ65jNr4Kw7DnFYdkyK4pw2HOJE4D10"
18
+ "vgN9MVzjMn5ehD1LoDQVnDO_kkTdmCmQbOFhjxwJMnE",
19
+ "x8blYyo4zMs0Vm8sXy1XFDeKPIMD34yPw1_LO1vv5z4"
20
20
  ],
21
- "createdAt": "2025-12-27T13:57:26.882Z"
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
- }