javascript-solid-server 0.0.6 → 0.0.8

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/README.md CHANGED
@@ -1,209 +1,230 @@
1
- # Vision
1
+ # JavaScript Solid Server
2
2
 
3
- The goal of this project is to create a hyper-modern, performant, and minimalist JavaScript Solid server. While drawing inspiration from Node Solid Server (NSS), this new implementation will address its shortcomings and prioritize scalability, modularity, and developer usability.
3
+ A minimal, fast, JSON-LD native Solid server.
4
4
 
5
- ## Key Objectives
5
+ ## Philosophy: JSON-LD First
6
6
 
7
- - **Performance First:** Capable of handling enterprise-scale loads, targeting thousands to millions of users.
8
- - **Minimalist Design:** Remove unused and experimental features; focus on what matters most.
9
- - **Modularity:** Clear separation of identity, authentication, storage, and onboarding.
10
- - **Developer Friendly:** Clean, well-documented, and extensible codebase that adheres to the Solid specification.
11
- - **Modern Tooling:** Leverage async/await, native modules, fast HTTP servers like Fastify, and cutting-edge JavaScript runtimes.
12
- - **HTTP Simplicity:** Prioritize simple HTTP/1.1 compatibility for maximum interoperability.
13
- - **Frontend Agnostic:** Work with any frontend or application layer via standardized APIs.
14
- - **Testable and CI Ready:** Fully integrated with Solid test suites and modern CI/CD pipelines.
7
+ This is a **JSON-LD native implementation**. Unlike traditional Solid servers that treat Turtle as the primary format and convert to/from it, this server:
15
8
 
16
- ---
9
+ - **Stores everything as JSON-LD** - No RDF parsing overhead for standard operations
10
+ - **Serves JSON-LD by default** - Modern web applications can consume responses directly
11
+ - **Content negotiation is optional** - Enable Turtle support with `{ conneg: true }` when needed
12
+ - **Fast by design** - Skip the RDF parsing tax when you don't need it
17
13
 
18
- # ARCHITECTURE
14
+ ### Why JSON-LD First?
19
15
 
20
- ## Overview
16
+ 1. **Performance**: JSON parsing is native to JavaScript - no external RDF libraries needed for basic operations
17
+ 2. **Simplicity**: JSON-LD is valid JSON - works with any JSON tooling
18
+ 3. **Web-native**: Browsers and web apps understand JSON natively
19
+ 4. **Semantic web ready**: JSON-LD is a W3C standard RDF serialization
21
20
 
22
- The architecture is inspired by NSS but modernized and streamlined. Each subsystem is designed to operate independently and follow the single-responsibility principle.
21
+ ### When to Enable Content Negotiation
23
22
 
24
- ### Components
23
+ Enable `conneg: true` when:
24
+ - Interoperating with Turtle-based Solid apps
25
+ - Serving data to legacy Solid clients
26
+ - Running conformance tests that require Turtle support
25
27
 
26
- - **HTTP Layer**
28
+ ```javascript
29
+ import { createServer } from './src/server.js';
27
30
 
28
- - Fastify server
29
- - Routing and middleware based on HTTP verbs and Solid operations
30
- - Blazingly fast, with benchmarks from the start
31
+ // Default: JSON-LD only (fast)
32
+ const server = createServer();
31
33
 
32
- - **Identity Provider (IDP)**
33
-
34
- - Handles Pod based WebIDs
35
- - Handles external WebIDs
36
- - Minimal by default, extendable via plugins
37
-
38
- - **Authenticaion Module (AUthn)**
39
-
40
- - Handles WebID-based authentication, including WebID-TLS
41
- - OIDC-compliant with modular Authentication
42
- - Single sign-on including WebID-TLS
43
-
44
- - **Authorization Module (Authz)**
45
-
46
- - Supports Web Access Control (WAC)
47
- - Token-based permissions model
48
- - Modular Authorization system
49
-
50
- - **Storage Engine**
51
-
52
- - Modular backend adapters (e.g. file system, S3, memory)
53
- - POD-level quota management (optional)
54
- - Interoperable with existing Cloud
55
-
56
- - **Account and Onboarding**
57
- - API-first registration
58
- - Public, private, invite modes
59
- - Extensible account templates
60
-
61
- ### Deployment Model
62
-
63
- - Works as a single binary or serverless function
64
- - Container-friendly (Docker, Deno, etc.)
65
- - CLI for local dev setup and testing
66
-
67
- ### Separation of Concerns
68
-
69
- - Each subsystem lives in its own module/package
70
- - Clear boundaries between IDP and storage
71
- - Frontend-independent API endpoints
72
-
73
- ### Compatibility
74
-
75
- - Solid-compliant, LWS Compliant
76
- - API parity with NSS where applicable
77
- - API parity with CSS where applicable
78
-
79
- ---
80
-
81
- # MVP Implementation
34
+ // With Turtle support (for interoperability)
35
+ const serverWithConneg = createServer({ conneg: true });
36
+ ```
82
37
 
83
- This is a minimal viable product (MVP) implementation of the JavaScriptSolid server. It includes the core components needed to demonstrate the concept while omitting some of the more complex features for simplicity.
38
+ ## Features
39
+
40
+ ### Implemented (v0.0.8)
41
+
42
+ - **LDP CRUD Operations** - GET, PUT, POST, DELETE, HEAD
43
+ - **N3 Patch** - Solid's native patch format for RDF updates
44
+ - **Container Management** - Create, list, and manage containers
45
+ - **Multi-user Pods** - Create pods at `/<username>/`
46
+ - **WebID Profiles** - JSON-LD structured data in HTML at pod root
47
+ - **Web Access Control (WAC)** - `.acl` file-based authorization
48
+ - **Solid-OIDC Resource Server** - Accept DPoP-bound access tokens from external IdPs
49
+ - **Simple Auth Tokens** - Built-in token authentication for development
50
+ - **Content Negotiation** - Optional Turtle <-> JSON-LD conversion
51
+ - **CORS Support** - Full cross-origin resource sharing
52
+
53
+ ### HTTP Methods
54
+
55
+ | Method | Support |
56
+ |--------|---------|
57
+ | GET | Full - Resources and containers |
58
+ | HEAD | Full |
59
+ | PUT | Full - Create/update resources |
60
+ | POST | Full - Create in containers |
61
+ | DELETE | Full |
62
+ | PATCH | N3 Patch format |
63
+ | OPTIONS | Full with CORS |
84
64
 
85
65
  ## Getting Started
86
66
 
87
67
  ### Prerequisites
88
68
 
89
- - Node.js 18 or higher
69
+ - Node.js 18+
90
70
 
91
71
  ### Installation
92
72
 
93
73
  ```bash
94
- # Clone the repository
95
- git clone https://github.com/yourusername/javascript-solid-server.git
96
- cd javascript-solid-server
97
-
98
- # Install dependencies
99
74
  npm install
100
75
  ```
101
76
 
102
- ### Running the Server
77
+ ### Running
103
78
 
104
79
  ```bash
105
- # Start the server
80
+ # Start server (default port 3000)
106
81
  npm start
107
- ```
108
82
 
109
- The server will be available at http://localhost:3000 by default.
83
+ # Development mode with watch
84
+ npm dev
85
+ ```
110
86
 
111
- ## Features Included in MVP
87
+ ### Creating a Pod
112
88
 
113
- - **HTTP Server**: Based on Fastify for high performance
114
- - **Basic Identity Provider**: Simple user registration and login with JWT tokens
115
- - **Simple Authorization**: Basic implementation of WAC (Web Access Control)
116
- - **File-based Storage**: Local filesystem storage for Solid resources
117
- - **Basic Solid Protocol Support**: GET, PUT, DELETE, PATCH, and HEAD operations
89
+ ```bash
90
+ curl -X POST http://localhost:3000/.pods \
91
+ -H "Content-Type: application/json" \
92
+ -d '{"name": "alice"}'
93
+ ```
118
94
 
119
- ## Features Omitted in MVP (to be added later)
95
+ Response:
96
+ ```json
97
+ {
98
+ "name": "alice",
99
+ "webId": "http://localhost:3000/alice/#me",
100
+ "podUri": "http://localhost:3000/alice/",
101
+ "token": "eyJ..."
102
+ }
103
+ ```
120
104
 
121
- 1. WebID-TLS Authentication
122
- 2. Full OIDC implementation
123
- 3. Advanced WAC features and ACL file parsing
124
- 4. Quotas and resource limits
125
- 5. Advanced container management
126
- 6. SPARQL and N3 Patch support
127
- 7. Notification systems
105
+ ### Using the Pod
128
106
 
129
- ## API Usage Examples
107
+ ```bash
108
+ # Read public profile
109
+ curl http://localhost:3000/alice/
130
110
 
131
- ### User Registration
111
+ # Write to pod (with token)
112
+ curl -X PUT http://localhost:3000/alice/public/data.json \
113
+ -H "Authorization: Bearer YOUR_TOKEN" \
114
+ -H "Content-Type: application/ld+json" \
115
+ -d '{"@id": "#data", "http://example.org/value": 42}'
132
116
 
133
- ```bash
134
- curl -X POST http://localhost:3000/register \
135
- -H "Content-Type: application/json" \
136
- -d '{"username": "alice", "password": "secret", "email": "alice@example.com"}'
117
+ # Read back
118
+ curl http://localhost:3000/alice/public/data.json
137
119
  ```
138
120
 
139
- ### Login
121
+ ### PATCH with N3
140
122
 
141
123
  ```bash
142
- curl -X POST http://localhost:3000/login \
143
- -H "Content-Type: application/json" \
144
- -d '{"username": "alice", "password": "secret"}'
124
+ curl -X PATCH http://localhost:3000/alice/public/data.json \
125
+ -H "Authorization: Bearer YOUR_TOKEN" \
126
+ -H "Content-Type: text/n3" \
127
+ -d '@prefix solid: <http://www.w3.org/ns/solid/terms#>.
128
+ _:patch a solid:InsertDeletePatch;
129
+ solid:inserts { <#data> <http://example.org/name> "Updated" }.'
145
130
  ```
146
131
 
147
- ### Accessing Resources
132
+ ## Pod Structure
148
133
 
149
- ```bash
150
- # Get a resource
151
- curl -X GET http://localhost:3000/alice/profile \
152
- -H "Authorization: Bearer YOUR_TOKEN_HERE"
153
-
154
- # Create or update a resource
155
- curl -X PUT http://localhost:3000/alice/profile \
156
- -H "Authorization: Bearer YOUR_TOKEN_HERE" \
157
- -H "Content-Type: text/turtle" \
158
- -d '@prefix foaf: <http://xmlns.com/foaf/0.1/>. <#me> a foaf:Person; foaf:name "Alice".'
134
+ ```
135
+ /alice/
136
+ ├── index.html # WebID profile (HTML with JSON-LD)
137
+ ├── .acl # Root ACL (owner + public read)
138
+ ├── inbox/ # Notifications (public append)
139
+ │ └── .acl
140
+ ├── public/ # Public files
141
+ ├── private/ # Private files (owner only)
142
+ │ └── .acl
143
+ └── settings/ # User preferences (owner only)
144
+ ├── .acl
145
+ ├── prefs
146
+ ├── publicTypeIndex
147
+ └── privateTypeIndex
159
148
  ```
160
149
 
161
- ## Performance Benchmarking
150
+ ## Authentication
162
151
 
163
- This project includes a comprehensive benchmarking tool to measure server performance under various loads.
152
+ ### Simple Tokens (Development)
164
153
 
165
- ### Running the Benchmark
154
+ Use the token returned from pod creation:
166
155
 
167
156
  ```bash
168
- # Make sure the server is running in a separate terminal
169
- npm start
170
-
171
- # In another terminal, run the benchmark
172
- npm run benchmark
157
+ curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:3000/alice/private/
173
158
  ```
174
159
 
175
- The benchmark will:
160
+ ### Solid-OIDC (Production)
161
+
162
+ The server accepts DPoP-bound access tokens from external Solid identity providers:
163
+
164
+ ```bash
165
+ curl -H "Authorization: DPoP ACCESS_TOKEN" \
166
+ -H "DPoP: DPOP_PROOF" \
167
+ http://localhost:3000/alice/private/
168
+ ```
176
169
 
177
- 1. Create multiple test users
178
- 2. Execute various operations (register, login, read, write, delete)
179
- 3. Measure response times for each operation type
180
- 4. Test different concurrency levels (1, 5, 10, 50, 100 users)
181
- 5. Calculate throughput (operations per second)
170
+ ## Configuration
182
171
 
183
- ### Visualizing Results
172
+ ```javascript
173
+ createServer({
174
+ logger: true, // Enable Fastify logging (default: true)
175
+ conneg: false // Enable content negotiation (default: false)
176
+ });
177
+ ```
184
178
 
185
- After running the benchmark, you can generate a visual report:
179
+ ## Running Tests
186
180
 
187
181
  ```bash
188
- # Generate an HTML report with charts
189
- npm run visualize benchmark-report-[timestamp].json
182
+ npm test
190
183
  ```
191
184
 
192
- Open the generated HTML file in a browser to see:
185
+ Currently passing: **105 tests**
186
+
187
+ ## Project Structure
193
188
 
194
- - Average response times for each operation type
195
- - Throughput metrics at different concurrency levels
196
- - Visual charts for easy performance analysis
189
+ ```
190
+ src/
191
+ ├── index.js # Entry point
192
+ ├── server.js # Fastify setup
193
+ ├── handlers/
194
+ │ ├── resource.js # GET, PUT, DELETE, HEAD, PATCH
195
+ │ └── container.js # POST, pod creation
196
+ ├── storage/
197
+ │ └── filesystem.js # File operations
198
+ ├── auth/
199
+ │ ├── middleware.js # Auth hook
200
+ │ ├── token.js # Simple token auth
201
+ │ └── solid-oidc.js # DPoP verification
202
+ ├── wac/
203
+ │ ├── parser.js # ACL parsing
204
+ │ └── checker.js # Permission checking
205
+ ├── ldp/
206
+ │ ├── headers.js # LDP Link headers
207
+ │ └── container.js # Container JSON-LD
208
+ ├── webid/
209
+ │ └── profile.js # WebID generation
210
+ ├── patch/
211
+ │ └── n3-patch.js # N3 Patch support
212
+ ├── rdf/
213
+ │ ├── turtle.js # Turtle <-> JSON-LD
214
+ │ └── conneg.js # Content negotiation
215
+ └── utils/
216
+ └── url.js # URL utilities
217
+ ```
197
218
 
198
- ### Customizing Benchmarks
219
+ ## Dependencies
199
220
 
200
- You can customize the benchmark parameters in `benchmark.js`:
221
+ Minimal dependencies for a fast, secure server:
201
222
 
202
- - Concurrent users levels
203
- - Operations per user
204
- - Test duration
205
- - Test user credentials
223
+ - **fastify** - High-performance HTTP server
224
+ - **fs-extra** - Enhanced file operations
225
+ - **jose** - JWT/JWK handling for Solid-OIDC
226
+ - **n3** - Turtle parsing (only used when conneg enabled)
206
227
 
207
- ## Contributing
228
+ ## License
208
229
 
209
- Contributions are welcome! Please feel free to submit a Pull Request.
230
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -12,7 +12,8 @@
12
12
  "dependencies": {
13
13
  "fastify": "^4.25.2",
14
14
  "fs-extra": "^11.2.0",
15
- "jose": "^6.1.3"
15
+ "jose": "^6.1.3",
16
+ "n3": "^1.26.0"
16
17
  },
17
18
  "engines": {
18
19
  "node": ">=18.0.0"
@@ -4,6 +4,7 @@ import { isContainer } from '../utils/url.js';
4
4
  import { generateProfile, generatePreferences, generateTypeIndex, serialize } from '../webid/profile.js';
5
5
  import { generateOwnerAcl, generatePrivateAcl, generateInboxAcl, serializeAcl } from '../wac/parser.js';
6
6
  import { createToken } from '../auth/token.js';
7
+ import { canAcceptInput, toJsonLd, getVaryHeader, RDF_TYPES } from '../rdf/conneg.js';
7
8
 
8
9
  /**
9
10
  * Handle POST request to container (create new resource)
@@ -16,6 +17,19 @@ export async function handlePost(request, reply) {
16
17
  return reply.code(405).send({ error: 'POST only allowed on containers' });
17
18
  }
18
19
 
20
+ const connegEnabled = request.connegEnabled || false;
21
+ const contentType = request.headers['content-type'] || '';
22
+
23
+ // Check if we can accept this input type
24
+ if (!canAcceptInput(contentType, connegEnabled)) {
25
+ return reply.code(415).send({
26
+ error: 'Unsupported Media Type',
27
+ message: connegEnabled
28
+ ? 'Supported types: application/ld+json, text/turtle, text/n3'
29
+ : 'Supported type: application/ld+json (enable conneg for Turtle support)'
30
+ });
31
+ }
32
+
19
33
  // Check container exists
20
34
  const stats = await storage.stat(urlPath);
21
35
  if (!stats || !stats.isDirectory) {
@@ -33,6 +47,7 @@ export async function handlePost(request, reply) {
33
47
  // Generate unique filename
34
48
  const filename = await storage.generateUniqueFilename(urlPath, slug, isCreatingContainer);
35
49
  const newPath = urlPath + filename + (isCreatingContainer ? '/' : '');
50
+ const resourceUrl = `${request.protocol}://${request.hostname}${newPath}`;
36
51
 
37
52
  let success;
38
53
  if (isCreatingContainer) {
@@ -49,6 +64,21 @@ export async function handlePost(request, reply) {
49
64
  } else {
50
65
  content = Buffer.from('');
51
66
  }
67
+
68
+ // Convert Turtle/N3 to JSON-LD if conneg enabled
69
+ const inputType = contentType.split(';')[0].trim().toLowerCase();
70
+ if (connegEnabled && (inputType === RDF_TYPES.TURTLE || inputType === RDF_TYPES.N3)) {
71
+ try {
72
+ const jsonLd = await toJsonLd(content, contentType, resourceUrl, connegEnabled);
73
+ content = Buffer.from(JSON.stringify(jsonLd, null, 2));
74
+ } catch (e) {
75
+ return reply.code(400).send({
76
+ error: 'Bad Request',
77
+ message: 'Invalid Turtle/N3 format: ' + e.message
78
+ });
79
+ }
80
+ }
81
+
52
82
  success = await storage.write(newPath, content);
53
83
  }
54
84
 
@@ -56,14 +86,15 @@ export async function handlePost(request, reply) {
56
86
  return reply.code(500).send({ error: 'Create failed' });
57
87
  }
58
88
 
59
- const location = `${request.protocol}://${request.hostname}${newPath}`;
60
89
  const origin = request.headers.origin;
61
90
 
62
91
  const headers = getAllHeaders({
63
92
  isContainer: isCreatingContainer,
64
- origin
93
+ origin,
94
+ connegEnabled
65
95
  });
66
- headers['Location'] = location;
96
+ headers['Location'] = resourceUrl;
97
+ headers['Vary'] = getVaryHeader(connegEnabled);
67
98
 
68
99
  Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
69
100
  return reply.code(201).send();