javascript-solid-server 0.0.87 → 0.0.88

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,12 +1,14 @@
1
1
  # JavaScript Solid Server
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/javascript-solid-server)](https://www.npmjs.com/package/javascript-solid-server)
4
+
3
5
  A minimal, fast, JSON-LD native Solid server.
4
6
 
5
7
  **[Documentation](https://javascriptsolidserver.github.io/docs/)** | **[GitHub](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer)**
6
8
 
7
9
  ## Features
8
10
 
9
- ### Implemented (v0.0.86)
11
+ ### Implemented
10
12
 
11
13
  - **Live Reload** - Auto-refresh browser on file changes (`--live-reload`)
12
14
  - **Read-Only Mode** - Disable write operations for static hosting (`--read-only`)
package/bin/jss.js CHANGED
@@ -80,6 +80,7 @@ program
80
80
  .option('--read-only', 'Disable PUT/DELETE/PATCH methods (read-only mode)')
81
81
  .option('--live-reload', 'Inject live reload script into HTML (auto-refresh on changes)')
82
82
  .option('-q, --quiet', 'Suppress log output')
83
+ .option('--log-level <level>', 'Log level: error, warn, info, debug (default: info)')
83
84
  .option('--print-config', 'Print configuration and exit')
84
85
  .action(async (options) => {
85
86
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.87",
3
+ "version": "0.0.88",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -13,6 +13,28 @@ const DEFAULT_DID_RESOLVER = 'https://nostr.social/.well-known/did/nostr';
13
13
  // Cache for resolved DIDs (pubkey -> webId or null)
14
14
  const cache = new Map();
15
15
  const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
16
+ const FAILURE_CACHE_TTL = 60 * 1000; // 1 minute for failed lookups
17
+
18
+ // Rate-limit repeated error logs (key -> { count, lastLogged })
19
+ const errorLogTracker = new Map();
20
+ const ERROR_LOG_INTERVAL = 60_000;
21
+
22
+ function rateLimitedError(key, message) {
23
+ const now = Date.now();
24
+ const entry = errorLogTracker.get(key);
25
+ if (entry && now - entry.lastLogged < ERROR_LOG_INTERVAL) {
26
+ entry.count++;
27
+ return;
28
+ }
29
+ // Clean up stale entries while we're here
30
+ for (const [k, v] of errorLogTracker) {
31
+ if (now - v.lastLogged > ERROR_LOG_INTERVAL) errorLogTracker.delete(k);
32
+ }
33
+ const suppressed = entry ? entry.count : 0;
34
+ const suffix = suppressed > 0 ? ` (${suppressed} similar suppressed)` : '';
35
+ console.error(`${message}${suffix}`);
36
+ errorLogTracker.set(key, { count: 0, lastLogged: now });
37
+ }
16
38
 
17
39
  /**
18
40
  * Fetch with timeout
@@ -41,11 +63,15 @@ export async function resolveDidNostrToWebId(pubkey, resolverUrl = DEFAULT_DID_R
41
63
  return null;
42
64
  }
43
65
 
44
- // Check cache
66
+ // Check cache (lazy eviction of expired entries)
45
67
  const cacheKey = pubkey.toLowerCase();
46
68
  const cached = cache.get(cacheKey);
47
- if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
48
- return cached.webId;
69
+ if (cached) {
70
+ const ttl = cached.failureTtl ? FAILURE_CACHE_TTL : CACHE_TTL;
71
+ if (Date.now() - cached.timestamp < ttl) {
72
+ return cached.webId;
73
+ }
74
+ cache.delete(cacheKey);
49
75
  }
50
76
 
51
77
  try {
@@ -93,8 +119,9 @@ export async function resolveDidNostrToWebId(pubkey, resolverUrl = DEFAULT_DID_R
93
119
  return null;
94
120
 
95
121
  } catch (err) {
96
- // Network error or timeout - don't cache failures
97
- console.error(`DID resolution error for ${pubkey}:`, err.message);
122
+ // Cache failures with short TTL to avoid hammering a down service
123
+ cache.set(cacheKey, { webId: null, timestamp: Date.now(), failureTtl: true });
124
+ rateLimitedError(`did:${pubkey.substring(0, 8)}`, `DID resolution error for ${pubkey}: ${err.message}`);
98
125
  return null;
99
126
  }
100
127
  }
@@ -148,7 +175,7 @@ async function verifyWebIdBacklink(webId, pubkey) {
148
175
  return false;
149
176
 
150
177
  } catch (err) {
151
- console.error(`WebID backlink verification error for ${webId}:`, err.message);
178
+ rateLimitedError(`backlink:${webId}`, `WebID backlink verification error for ${webId}: ${err.message}`);
152
179
  return false;
153
180
  }
154
181
  }
package/src/config.js CHANGED
@@ -85,6 +85,7 @@ export const defaults = {
85
85
  // Logging
86
86
  logger: true,
87
87
  quiet: false,
88
+ logLevel: 'info',
88
89
 
89
90
  // Paths
90
91
  configPath: './.jss',
@@ -103,6 +104,7 @@ const envMap = {
103
104
  JSS_CONNEG: 'conneg',
104
105
  JSS_NOTIFICATIONS: 'notifications',
105
106
  JSS_QUIET: 'quiet',
107
+ JSS_LOG_LEVEL: 'logLevel',
106
108
  JSS_CONFIG_PATH: 'configPath',
107
109
  JSS_IDP: 'idp',
108
110
  JSS_IDP_ISSUER: 'idpIssuer',
@@ -228,6 +230,13 @@ export async function loadConfig(cliOptions = {}, configFile = null) {
228
230
  config.logger = false;
229
231
  }
230
232
 
233
+ // Validate log level
234
+ const validLevels = ['fatal', 'error', 'warn', 'info', 'debug', 'trace'];
235
+ if (!validLevels.includes(config.logLevel)) {
236
+ console.warn(`Invalid log level '${config.logLevel}', falling back to 'info'. Valid levels: ${validLevels.join(', ')}`);
237
+ config.logLevel = 'info';
238
+ }
239
+
231
240
  // Mashlib requires content negotiation for Turtle support
232
241
  if (config.mashlib || config.mashlibCdn) {
233
242
  config.conneg = true;
package/src/server.js CHANGED
@@ -89,8 +89,10 @@ export function createServer(options = {}) {
89
89
  }
90
90
 
91
91
  // Fastify options
92
+ const loggerEnabled = options.logger ?? true;
92
93
  const fastifyOptions = {
93
- logger: options.logger ?? true,
94
+ logger: loggerEnabled ? { level: options.logLevel || 'info' } : false,
95
+ disableRequestLogging: true,
94
96
  trustProxy: true,
95
97
  // Handle raw body for non-JSON content
96
98
  bodyLimit: 10 * 1024 * 1024 // 10MB
@@ -169,6 +171,19 @@ export function createServer(options = {}) {
169
171
  }
170
172
  });
171
173
 
174
+ // Unified access log — one line per request
175
+ fastify.addHook('onResponse', async (request, reply) => {
176
+ if (!request.log.isLevelEnabled('info')) return;
177
+ request.log.info({
178
+ req: { method: request.method, url: request.url, remoteAddress: request.ip },
179
+ res: { statusCode: reply.statusCode },
180
+ responseTime: Math.round(reply.elapsedTime * 100) / 100,
181
+ userAgent: request.headers['user-agent'] || undefined,
182
+ referrer: request.headers.referer || undefined,
183
+ contentLength: reply.getHeader('content-length') || undefined,
184
+ }, `${request.method} ${request.url} ${reply.statusCode} ${Math.round(reply.elapsedTime)}ms`);
185
+ });
186
+
172
187
  // Register WebSocket notifications plugin if enabled (or live reload needs it)
173
188
  if (notificationsEnabled || liveReloadEnabled) {
174
189
  fastify.register(notificationsPlugin);