fhirsmith 0.5.0 → 0.5.2

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/CHANGELOG.md CHANGED
@@ -5,13 +5,34 @@ All notable changes to the Health Intersections Node Server will be documented i
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [v0.5.1] - 2026-02-20
9
+
10
+ ### Added
11
+ - Improved logging of startup conditions and failure
12
+
13
+ ### Changed
14
+ - Fixed bad cron scheduled processing in XIG module
15
+
16
+ ### Tx Conformance Statement
17
+
18
+ FHIRsmith 0.5.1 passed all 1288 HL7 terminology service tests (modes tx.fhir.org,omop,general,snomed, tests v1.9.1-SNAPSHOT, runner v6.8.0)
19
+
20
+ ## [v0.5.2] - 2026-02-20
21
+
22
+ ### Changed
23
+ - Fixed bad count reference in XIG
24
+
25
+ ### Tx Conformance Statement
26
+
27
+ FHIRsmith 0.5.2 passed all 1288 HL7 terminology service tests (modes tx.fhir.org,omop,general,snomed, tests v1.9.1-SNAPSHOT, runner v6.8.0)
28
+
8
29
  ## [v0.5.0] - 2026-02-19
9
30
 
10
31
  ### Added
11
32
  - Prototype Implementation of $related operation
12
33
 
13
34
  ### Changed
14
- - A great deal of QA work preparing the server to run tx.fhir.org, which led to 100s of fixes
35
+ - A great deal of QA work preparing the server to run tx.fhir.org, which led to 100s of fixes
15
36
 
16
37
  ### Tx Conformance Statement
17
38
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fhirsmith",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "A Node.js server that provides a collection of tools to serve the FHIR ecosystem",
5
5
  "main": "server.js",
6
6
  "engines": {
package/server.js CHANGED
@@ -10,6 +10,7 @@ const express = require('express');
10
10
  // const cors = require('cors');
11
11
  const path = require('path');
12
12
  const fs = require('fs');
13
+ const os = require('os');
13
14
  const folders = require('./library/folder-setup'); // <-- ADD: load early
14
15
  const { statSync, readdirSync } = require('fs');
15
16
  const escape = require('escape-html');
@@ -27,6 +28,17 @@ try {
27
28
 
28
29
  const Logger = require('./library/logger');
29
30
  const serverLog = Logger.getInstance().child({ module: 'server' });
31
+ const packageJson = require('./package.json');
32
+
33
+ // Startup banner
34
+ const totalMemGB = (os.totalmem() / 1024 / 1024 / 1024).toFixed(1);
35
+ const freeMemGB = (os.freemem() / 1024 / 1024 / 1024).toFixed(1);
36
+ serverLog.info(`========================================`);
37
+ serverLog.info(`FHIRsmith v${packageJson.version} starting (PID ${process.pid})`);
38
+ serverLog.info(`Node.js ${process.version} on ${os.type()} ${os.release()} (${os.arch()})`);
39
+ serverLog.info(`Memory: ${freeMemGB} GB free / ${totalMemGB} GB total`);
40
+ serverLog.info(`Data directory: ${folders.dataDir()}`);
41
+ serverLog.info(`========================================`);
30
42
 
31
43
  const activeModules = config.modules ? Object.keys(config.modules)
32
44
  .filter(mod => config.modules[mod].enabled)
@@ -43,7 +55,6 @@ const PublisherModule = require('./publisher/publisher.js');
43
55
  const TokenModule = require('./token/token.js');
44
56
  const NpmProjectorModule = require('./npmprojector/npmprojector.js');
45
57
  const TXModule = require('./tx/tx.js');
46
- const packageJson = require('./package.json');
47
58
 
48
59
  const htmlServer = require('./library/html-server');
49
60
  const ServerStats = require("./stats");
@@ -81,6 +92,7 @@ async function initializeModules() {
81
92
  // Initialize SHL module
82
93
  if (config.modules?.shl?.enabled) {
83
94
  try {
95
+ serverLog.info('Initializing module: shl...');
84
96
  modules.shl = new SHLModule(stats);
85
97
  await modules.shl.initialize(config.modules.shl);
86
98
  app.use('/shl', modules.shl.router);
@@ -93,6 +105,7 @@ async function initializeModules() {
93
105
  // Initialize VCL module
94
106
  if (config.modules?.vcl?.enabled) {
95
107
  try {
108
+ serverLog.info('Initializing module: vcl...');
96
109
  modules.vcl = new VCLModule(stats);
97
110
  await modules.vcl.initialize(config.modules.vcl);
98
111
  app.use('/VCL', modules.vcl.router);
@@ -101,11 +114,12 @@ async function initializeModules() {
101
114
  throw error;
102
115
  }
103
116
  }
104
-
117
+
105
118
  // Initialize XIG module
106
119
  if (config.modules?.xig?.enabled) {
107
120
  try {
108
- await xigModule.initializeXigModule(stats);
121
+ serverLog.info('Initializing module: xig...');
122
+ await xigModule.initializeXigModule(stats, config.modules.xig);
109
123
  app.use('/xig', xigModule.router);
110
124
  modules.xig = xigModule;
111
125
  } catch (error) {
@@ -117,6 +131,7 @@ async function initializeModules() {
117
131
  // Initialize Packages module
118
132
  if (config.modules?.packages?.enabled) {
119
133
  try {
134
+ serverLog.info('Initializing module: packages...');
120
135
  modules.packages = new PackagesModule(stats);
121
136
  await modules.packages.initialize(config.modules.packages);
122
137
  app.use('/packages', modules.packages.router);
@@ -130,6 +145,7 @@ async function initializeModules() {
130
145
  // Initialize Registry module
131
146
  if (config.modules?.registry?.enabled) {
132
147
  try {
148
+ serverLog.info('Initializing module: registry...');
133
149
  modules.registry = new RegistryModule(stats);
134
150
  await modules.registry.initialize(config.modules.registry);
135
151
  app.use('/tx-reg', modules.registry.router);
@@ -142,6 +158,7 @@ async function initializeModules() {
142
158
  // Initialize Publisher module
143
159
  if (config.modules?.publisher?.enabled) {
144
160
  try {
161
+ serverLog.info('Initializing module: publisher...');
145
162
  modules.publisher = new PublisherModule(stats);
146
163
  await modules.publisher.initialize(config.modules.publisher);
147
164
  app.use('/publisher', modules.publisher.router);
@@ -154,6 +171,7 @@ async function initializeModules() {
154
171
  // Initialize Token module
155
172
  if (config.modules?.token?.enabled) {
156
173
  try {
174
+ serverLog.info('Initializing module: token...');
157
175
  modules.token = new TokenModule(stats);
158
176
  await modules.token.initialize(config.modules.token);
159
177
  app.use('/token', modules.token.router);
@@ -166,6 +184,7 @@ async function initializeModules() {
166
184
  // Initialize NpmProjector module
167
185
  if (config.modules?.npmprojector?.enabled) {
168
186
  try {
187
+ serverLog.info('Initializing module: npmprojector...');
169
188
  modules.npmprojector = new NpmProjectorModule(stats);
170
189
  await modules.npmprojector.initialize(config.modules.npmprojector);
171
190
  const basePath = NpmProjectorModule.getBasePath(config.modules.npmprojector);
@@ -181,6 +200,7 @@ async function initializeModules() {
181
200
  // because it supports multiple endpoints at different paths
182
201
  if (config.modules?.tx?.enabled) {
183
202
  try {
203
+ serverLog.info('Initializing module: tx...');
184
204
  modules.tx = new TXModule(stats);
185
205
  await modules.tx.initialize(config.modules.tx, app);
186
206
  } catch (error) {
@@ -365,6 +385,13 @@ async function buildRootPageContent() {
365
385
  // eslint-disable-next-line no-unused-vars
366
386
  process.on('unhandledRejection', (reason, promise) => {
367
387
  console.error('Unhandled Rejection:', reason);
388
+ serverLog.error('Unhandled Rejection:', reason);
389
+ });
390
+
391
+ process.on('uncaughtException', (error) => {
392
+ console.error('FATAL - Uncaught Exception:', error);
393
+ serverLog.error('FATAL - Uncaught Exception:', error);
394
+ process.exitCode = 1;
368
395
  });
369
396
 
370
397
  app.get('/', async (req, res) => {
@@ -382,7 +409,7 @@ app.get('/', async (req, res) => {
382
409
  }
383
410
 
384
411
  const content = await buildRootPageContent();
385
-
412
+
386
413
  // Build basic stats for root page
387
414
  const stats = {
388
415
  version: packageJson.version,
@@ -431,7 +458,7 @@ app.get('/', async (req, res) => {
431
458
  Object.keys(enabledModules)
432
459
  .filter(m => m !== 'tx')
433
460
  .map(m => [
434
- m,
461
+ m,
435
462
  m === 'vcl' ? '/VCL' : `/${m}`
436
463
  ])
437
464
  ),
@@ -542,8 +569,10 @@ async function startServer() {
542
569
  modules.packages.startInitialCrawler();
543
570
  }
544
571
  } catch (error) {
545
- serverLog.error('Failed to start server:', error);
546
- process.exit(1);
572
+ console.error('FATAL - Failed to start server:', error);
573
+ serverLog.error('FATAL - Failed to start server:', error);
574
+ // Give the logger a moment to flush before exiting
575
+ setTimeout(() => process.exit(1), 500);
547
576
  }
548
577
  }
549
578
 
package/xig/xig.js CHANGED
@@ -2213,24 +2213,24 @@ router.get('/:packagePid/:resourceType/:resourceId', async (req, res) => {
2213
2213
  const start = Date.now();
2214
2214
  try {
2215
2215
 
2216
- const { packagePid, resourceType, resourceId } = req.params;
2216
+ const { packagePid, resourceType, resourceId } = req.params;
2217
2217
 
2218
- // Check if this looks like a package/resource pattern
2219
- // Package PIDs typically contain dots and pipes: hl7.fhir.uv.extensions|current
2220
- // Resource types are FHIR resource names: StructureDefinition, ValueSet, etc.
2218
+ // Check if this looks like a package/resource pattern
2219
+ // Package PIDs typically contain dots and pipes: hl7.fhir.uv.extensions|current
2220
+ // Resource types are FHIR resource names: StructureDefinition, ValueSet, etc.
2221
2221
 
2222
- const isPackagePidFormat = packagePid.includes('.') || packagePid.includes('|');
2223
- const isFhirResourceType = /^[A-Z][a-zA-Z]+$/.test(resourceType);
2222
+ const isPackagePidFormat = packagePid.includes('.') || packagePid.includes('|');
2223
+ const isFhirResourceType = /^[A-Z][a-zA-Z]+$/.test(resourceType);
2224
2224
 
2225
- if (isPackagePidFormat && isFhirResourceType) {
2226
- // This looks like a legacy resource URL, redirect to the proper format
2227
- res.redirect(301, `/xig/resource/${packagePid}/${resourceType}/${resourceId}`);
2228
- } else {
2229
- // Not a resource URL pattern, return 404
2230
- res.status(404).send('Not Found');
2231
- }
2225
+ if (isPackagePidFormat && isFhirResourceType) {
2226
+ // This looks like a legacy resource URL, redirect to the proper format
2227
+ res.redirect(301, `/xig/resource/${packagePid}/${resourceType}/${resourceId}`);
2228
+ } else {
2229
+ // Not a resource URL pattern, return 404
2230
+ res.status(404).send('Not Found');
2231
+ }
2232
2232
  } finally {
2233
- this.stats.countRequest(':id', Date.now() - start);
2233
+ globalStats.countRequest(':id', Date.now() - start);
2234
2234
  }
2235
2235
  });
2236
2236
 
@@ -2333,74 +2333,74 @@ router.get('/stats', async (req, res) => {
2333
2333
  const start = Date.now();
2334
2334
  try {
2335
2335
 
2336
- const startTime = Date.now(); // Add this at the very beginning
2337
-
2338
- try {
2336
+ const startTime = Date.now(); // Add this at the very beginning
2339
2337
 
2340
- const [dbInfo, tableCounts] = await Promise.all([
2341
- getDatabaseInfo(),
2342
- getDatabaseTableCounts()
2343
- ]);
2338
+ try {
2344
2339
 
2345
- const statsData = {
2346
- cache: getCacheStats(),
2347
- database: dbInfo,
2348
- databaseAge: getDatabaseAgeInfo(),
2349
- tableCounts: tableCounts,
2350
- requests: getRequestStats()
2351
- };
2340
+ const [dbInfo, tableCounts] = await Promise.all([
2341
+ getDatabaseInfo(),
2342
+ getDatabaseTableCounts()
2343
+ ]);
2344
+
2345
+ const statsData = {
2346
+ cache: getCacheStats(),
2347
+ database: dbInfo,
2348
+ databaseAge: getDatabaseAgeInfo(),
2349
+ tableCounts: tableCounts,
2350
+ requests: getRequestStats()
2351
+ };
2352
2352
 
2353
- const content = buildStatsTable(statsData);
2354
-
2355
- let introContent = '';
2356
- const lastAttempt = getLastUpdateAttempt();
2357
-
2358
- if (statsData.databaseAge.daysOld !== null && statsData.databaseAge.daysOld > 1) {
2359
- introContent += `<div class="alert alert-warning">`;
2360
- introContent += `<strong>⚠ Database is ${statsData.databaseAge.daysOld} days old.</strong> `;
2361
- introContent += `Automatic updates are scheduled daily at 2 AM. `;
2362
- if (lastAttempt) {
2363
- if (lastAttempt.status === 'failed') {
2364
- introContent += `<br><strong>Last update attempt failed</strong> at ${new Date(lastAttempt.timestamp).toLocaleString()}: `;
2365
- introContent += `${escape(lastAttempt.error || 'Unknown error')}`;
2366
- if (lastAttempt.downloadMeta && lastAttempt.downloadMeta.httpStatus) {
2367
- introContent += ` (HTTP ${lastAttempt.downloadMeta.httpStatus})`;
2353
+ const content = buildStatsTable(statsData);
2354
+
2355
+ let introContent = '';
2356
+ const lastAttempt = getLastUpdateAttempt();
2357
+
2358
+ if (statsData.databaseAge.daysOld !== null && statsData.databaseAge.daysOld > 1) {
2359
+ introContent += `<div class="alert alert-warning">`;
2360
+ introContent += `<strong>⚠ Database is ${statsData.databaseAge.daysOld} days old.</strong> `;
2361
+ introContent += `Automatic updates are scheduled daily at 2 AM. `;
2362
+ if (lastAttempt) {
2363
+ if (lastAttempt.status === 'failed') {
2364
+ introContent += `<br><strong>Last update attempt failed</strong> at ${new Date(lastAttempt.timestamp).toLocaleString()}: `;
2365
+ introContent += `${escape(lastAttempt.error || 'Unknown error')}`;
2366
+ if (lastAttempt.downloadMeta && lastAttempt.downloadMeta.httpStatus) {
2367
+ introContent += ` (HTTP ${lastAttempt.downloadMeta.httpStatus})`;
2368
+ }
2369
+ } else if (lastAttempt.status === 'success') {
2370
+ introContent += `<br>Last successful update: ${new Date(lastAttempt.timestamp).toLocaleString()} `;
2371
+ introContent += `(file age based on filesystem mtime)`;
2368
2372
  }
2369
- } else if (lastAttempt.status === 'success') {
2370
- introContent += `<br>Last successful update: ${new Date(lastAttempt.timestamp).toLocaleString()} `;
2371
- introContent += `(file age based on filesystem mtime)`;
2373
+ } else {
2374
+ introContent += `<br>No update attempts recorded since server started.`;
2372
2375
  }
2373
- } else {
2374
- introContent += `<br>No update attempts recorded since server started.`;
2376
+ introContent += `</div>`;
2377
+ } else if (lastAttempt && lastAttempt.status === 'failed') {
2378
+ // DB is fresh but last attempt failed — still worth showing
2379
+ introContent += `<div class="alert alert-warning">`;
2380
+ introContent += `<strong>Last update attempt failed</strong> at ${new Date(lastAttempt.timestamp).toLocaleString()}: `;
2381
+ introContent += `${escape(lastAttempt.error || 'Unknown error')}`;
2382
+ introContent += `</div>`;
2375
2383
  }
2376
- introContent += `</div>`;
2377
- } else if (lastAttempt && lastAttempt.status === 'failed') {
2378
- // DB is fresh but last attempt failed — still worth showing
2379
- introContent += `<div class="alert alert-warning">`;
2380
- introContent += `<strong>Last update attempt failed</strong> at ${new Date(lastAttempt.timestamp).toLocaleString()}: `;
2381
- introContent += `${escape(lastAttempt.error || 'Unknown error')}`;
2382
- introContent += `</div>`;
2383
- }
2384
2384
 
2385
- if (!statsData.cache.loaded) {
2386
- introContent += `<div class="alert alert-info">`;
2387
- introContent += `<strong>Info:</strong> Cache is still loading. Some statistics may be incomplete.`;
2388
- introContent += `</div>`;
2389
- }
2385
+ if (!statsData.cache.loaded) {
2386
+ introContent += `<div class="alert alert-info">`;
2387
+ introContent += `<strong>Info:</strong> Cache is still loading. Some statistics may be incomplete.`;
2388
+ introContent += `</div>`;
2389
+ }
2390
2390
 
2391
- const fullContent = introContent + content;
2391
+ const fullContent = introContent + content;
2392
2392
 
2393
- const stats = await gatherPageStatistics();
2394
- stats.processingTime = Date.now() - startTime;
2393
+ const stats = await gatherPageStatistics();
2394
+ stats.processingTime = Date.now() - startTime;
2395
2395
 
2396
- const html = renderPage('FHIR IG Statistics Status', fullContent, stats);
2397
- res.setHeader('Content-Type', 'text/html');
2398
- res.send(html);
2396
+ const html = renderPage('FHIR IG Statistics Status', fullContent, stats);
2397
+ res.setHeader('Content-Type', 'text/html');
2398
+ res.send(html);
2399
2399
 
2400
- } catch (error) {
2401
- xigLog.error(`Error generating stats page: ${error.message}`);
2402
- htmlServer.sendErrorResponse(res, 'xig', error);
2403
- }
2400
+ } catch (error) {
2401
+ xigLog.error(`Error generating stats page: ${error.message}`);
2402
+ htmlServer.sendErrorResponse(res, 'xig', error);
2403
+ }
2404
2404
  } finally {
2405
2405
  globalStats.countRequest('stats', Date.now() - start);
2406
2406
  }
@@ -2410,56 +2410,56 @@ router.get('/stats', async (req, res) => {
2410
2410
  router.get('/resource/:packagePid/:resourceType/:resourceId', async (req, res) => {
2411
2411
  const start = Date.now();
2412
2412
  try {
2413
- const startTime = Date.now(); // Add this at the very beginning
2414
- try {
2415
- const { packagePid, resourceType, resourceId } = req.params;
2413
+ const startTime = Date.now(); // Add this at the very beginning
2414
+ try {
2415
+ const { packagePid, resourceType, resourceId } = req.params;
2416
2416
 
2417
- // Convert URL-safe package PID back to database format (| to #)
2418
- const dbPackagePid = packagePid.replace(/\|/g, '#');
2417
+ // Convert URL-safe package PID back to database format (| to #)
2418
+ const dbPackagePid = packagePid.replace(/\|/g, '#');
2419
2419
 
2420
- if (!xigDb) {
2421
- throw new Error('Database not available');
2422
- }
2420
+ if (!xigDb) {
2421
+ throw new Error('Database not available');
2422
+ }
2423
2423
 
2424
- // Get package information first
2425
- const packageObj = getPackageByPid(dbPackagePid);
2426
- if (!packageObj) {
2427
- return res.status(404).send(renderPage('Resource Not Found',
2428
- `<div class="alert alert-danger">Unknown Package: ${escape(packagePid)}</div>`));
2429
- }
2424
+ // Get package information first
2425
+ const packageObj = getPackageByPid(dbPackagePid);
2426
+ if (!packageObj) {
2427
+ return res.status(404).send(renderPage('Resource Not Found',
2428
+ `<div class="alert alert-danger">Unknown Package: ${escape(packagePid)}</div>`));
2429
+ }
2430
2430
 
2431
- // Get resource details
2432
- const resourceQuery = `
2433
- SELECT * FROM Resources
2434
- WHERE PackageKey = ? AND ResourceType = ? AND Id = ?
2435
- `;
2431
+ // Get resource details
2432
+ const resourceQuery = `
2433
+ SELECT * FROM Resources
2434
+ WHERE PackageKey = ? AND ResourceType = ? AND Id = ?
2435
+ `;
2436
2436
 
2437
- const resourceData = await new Promise((resolve, reject) => {
2438
- xigDb.get(resourceQuery, [packageObj.PackageKey, resourceType, resourceId], (err, row) => {
2439
- if (err) reject(err);
2440
- else resolve(row);
2437
+ const resourceData = await new Promise((resolve, reject) => {
2438
+ xigDb.get(resourceQuery, [packageObj.PackageKey, resourceType, resourceId], (err, row) => {
2439
+ if (err) reject(err);
2440
+ else resolve(row);
2441
+ });
2441
2442
  });
2442
- });
2443
2443
 
2444
- if (!resourceData) {
2445
- return res.status(404).send(renderPage('Resource Not Found',
2446
- `<div class="alert alert-danger">Unknown Resource: ${escape(resourceType)}/${escape(resourceId)} in package ${escape(packagePid)}</div>`));
2447
- }
2444
+ if (!resourceData) {
2445
+ return res.status(404).send(renderPage('Resource Not Found',
2446
+ `<div class="alert alert-danger">Unknown Resource: ${escape(resourceType)}/${escape(resourceId)} in package ${escape(packagePid)}</div>`));
2447
+ }
2448
2448
 
2449
- // Build the resource detail page
2450
- const content = await buildResourceDetailPage(packageObj, resourceData, req.secure);
2451
- const title = `${resourceType}/${resourceId}`;
2452
- const stats = await gatherPageStatistics();
2453
- stats.processingTime = Date.now() - startTime;
2449
+ // Build the resource detail page
2450
+ const content = await buildResourceDetailPage(packageObj, resourceData, req.secure);
2451
+ const title = `${resourceType}/${resourceId}`;
2452
+ const stats = await gatherPageStatistics();
2453
+ stats.processingTime = Date.now() - startTime;
2454
2454
 
2455
- const html = renderPage(title, content, stats);
2456
- res.setHeader('Content-Type', 'text/html');
2457
- res.send(html);
2455
+ const html = renderPage(title, content, stats);
2456
+ res.setHeader('Content-Type', 'text/html');
2457
+ res.send(html);
2458
2458
 
2459
- } catch (error) {
2460
- xigLog.error(`Error rendering resource detail page: ${error.message}`);
2461
- htmlServer.sendErrorResponse(res, 'xig', error);
2462
- }
2459
+ } catch (error) {
2460
+ xigLog.error(`Error rendering resource detail page: ${error.message}`);
2461
+ htmlServer.sendErrorResponse(res, 'xig', error);
2462
+ }
2463
2463
  } finally {
2464
2464
  globalStats.countRequest(':pid', Date.now() - start);
2465
2465
  }
@@ -2874,27 +2874,27 @@ router.get('/status', async (req, res) => {
2874
2874
  const start = Date.now();
2875
2875
  try {
2876
2876
 
2877
- try {
2878
- const dbInfo = await getDatabaseInfo();
2879
- await res.json({
2880
- status: 'OK',
2881
- database: dbInfo,
2882
- databaseAge: getDatabaseAgeInfo(),
2883
- downloadUrl: XIG_DB_URL,
2884
- localPath: XIG_DB_PATH,
2885
- cache: getCacheStats(),
2886
- updateInProgress: updateInProgress,
2887
- lastUpdateAttempt: getLastUpdateAttempt(),
2888
- updateHistory: getUpdateHistory()
2889
- });
2890
- } catch (error) {
2891
- res.status(500).json({
2892
- status: 'ERROR',
2893
- error: error.message,
2894
- cache: getCacheStats(),
2895
- updateHistory: getUpdateHistory()
2896
- });
2897
- }
2877
+ try {
2878
+ const dbInfo = await getDatabaseInfo();
2879
+ await res.json({
2880
+ status: 'OK',
2881
+ database: dbInfo,
2882
+ databaseAge: getDatabaseAgeInfo(),
2883
+ downloadUrl: XIG_DB_URL,
2884
+ localPath: XIG_DB_PATH,
2885
+ cache: getCacheStats(),
2886
+ updateInProgress: updateInProgress,
2887
+ lastUpdateAttempt: getLastUpdateAttempt(),
2888
+ updateHistory: getUpdateHistory()
2889
+ });
2890
+ } catch (error) {
2891
+ res.status(500).json({
2892
+ status: 'ERROR',
2893
+ error: error.message,
2894
+ cache: getCacheStats(),
2895
+ updateHistory: getUpdateHistory()
2896
+ });
2897
+ }
2898
2898
  } finally {
2899
2899
  globalStats.countRequest('stats', Date.now() - start);
2900
2900
  }
@@ -2929,7 +2929,7 @@ router.post('/update', async (req, res) => {
2929
2929
 
2930
2930
  let globalStats;
2931
2931
  // Initialize the XIG module
2932
- async function initializeXigModule(stats) {
2932
+ async function initializeXigModule(stats, xigConfig) {
2933
2933
  try {
2934
2934
  globalStats = stats;
2935
2935
  loadTemplate();
@@ -2944,13 +2944,15 @@ async function initializeXigModule(stats) {
2944
2944
  }
2945
2945
 
2946
2946
  if (globalStats) {
2947
- globalStats.addTask('XIG Download', describeCron(this.config.crawler.schedule));
2947
+ globalStats.addTask('XIG Download', describeCron('0 2 * * *'));
2948
2948
  }
2949
2949
  // Check if auto-update is enabled
2950
2950
  // Note: This assumes we're called only when XIG is enabled
2951
- cron.schedule('0 2 * * *', () => {
2952
- updateXigDatabase();
2953
- });
2951
+ if (xigConfig?.autoUpdate !== false) {
2952
+ cron.schedule('0 2 * * *', () => {
2953
+ updateXigDatabase();
2954
+ });
2955
+ }
2954
2956
 
2955
2957
  } catch (error) {
2956
2958
  xigLog.error(`XIG module initialization failed: ${error.message}`);