@harperfast/harper-pro 5.0.0-beta.5 → 5.0.0-beta.7

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.
Files changed (93) hide show
  1. package/core/bin/harper.js +1 -1
  2. package/core/bin/run.js +2 -2
  3. package/core/components/Application.ts +9 -5
  4. package/core/components/ApplicationScope.ts +3 -3
  5. package/core/components/componentLoader.ts +28 -15
  6. package/core/components/operations.js +13 -13
  7. package/core/components/operationsValidation.js +3 -3
  8. package/core/config/configUtils.js +20 -4
  9. package/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +3 -2
  10. package/core/resources/Resource.ts +17 -6
  11. package/core/resources/RocksTransactionLogStore.ts +8 -1
  12. package/core/resources/analytics/metadata.ts +1 -0
  13. package/core/resources/analytics/read.ts +1 -1
  14. package/core/resources/analytics/write.ts +43 -2
  15. package/core/resources/databases.ts +24 -15
  16. package/core/security/jsLoader.ts +182 -91
  17. package/core/server/REST.ts +20 -11
  18. package/core/server/http.ts +3 -3
  19. package/core/server/itc/serverHandlers.js +1 -1
  20. package/core/static/defaultConfig.yaml +1 -1
  21. package/core/utility/hdbTerms.ts +1 -0
  22. package/core/utility/logging/harper_logger.js +44 -11
  23. package/core/utility/logging/readLog.js +2 -2
  24. package/core/utility/npmUtilities.js +2 -2
  25. package/core/validation/configValidator.js +16 -8
  26. package/core/validation/readLogValidator.js +2 -2
  27. package/dist/core/bin/harper.js +1 -1
  28. package/dist/core/bin/run.js +2 -2
  29. package/dist/core/bin/run.js.map +1 -1
  30. package/dist/core/components/Application.js +9 -4
  31. package/dist/core/components/Application.js.map +1 -1
  32. package/dist/core/components/ApplicationScope.js +2 -2
  33. package/dist/core/components/ApplicationScope.js.map +1 -1
  34. package/dist/core/components/componentLoader.js +30 -16
  35. package/dist/core/components/componentLoader.js.map +1 -1
  36. package/dist/core/components/operations.js +13 -13
  37. package/dist/core/components/operations.js.map +1 -1
  38. package/dist/core/components/operationsValidation.js +3 -3
  39. package/dist/core/components/operationsValidation.js.map +1 -1
  40. package/dist/core/config/configUtils.js +23 -3
  41. package/dist/core/config/configUtils.js.map +1 -1
  42. package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +3 -2
  43. package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
  44. package/dist/core/resources/Resource.js +11 -4
  45. package/dist/core/resources/Resource.js.map +1 -1
  46. package/dist/core/resources/RocksTransactionLogStore.js +8 -1
  47. package/dist/core/resources/RocksTransactionLogStore.js.map +1 -1
  48. package/dist/core/resources/analytics/metadata.js +1 -0
  49. package/dist/core/resources/analytics/metadata.js.map +1 -1
  50. package/dist/core/resources/analytics/read.js +1 -1
  51. package/dist/core/resources/analytics/read.js.map +1 -1
  52. package/dist/core/resources/analytics/write.js +42 -0
  53. package/dist/core/resources/analytics/write.js.map +1 -1
  54. package/dist/core/resources/databases.js +19 -13
  55. package/dist/core/resources/databases.js.map +1 -1
  56. package/dist/core/security/jsLoader.js +167 -81
  57. package/dist/core/security/jsLoader.js.map +1 -1
  58. package/dist/core/server/REST.js +17 -10
  59. package/dist/core/server/REST.js.map +1 -1
  60. package/dist/core/server/http.js +2 -2
  61. package/dist/core/server/http.js.map +1 -1
  62. package/dist/core/server/itc/serverHandlers.js +1 -1
  63. package/dist/core/server/itc/serverHandlers.js.map +1 -1
  64. package/dist/core/utility/hdbTerms.js +1 -0
  65. package/dist/core/utility/hdbTerms.js.map +1 -1
  66. package/dist/core/utility/logging/harper_logger.js +47 -11
  67. package/dist/core/utility/logging/harper_logger.js.map +1 -1
  68. package/dist/core/utility/logging/readLog.js +2 -2
  69. package/dist/core/utility/logging/readLog.js.map +1 -1
  70. package/dist/core/utility/npmUtilities.js +2 -2
  71. package/dist/core/utility/npmUtilities.js.map +1 -1
  72. package/dist/core/validation/configValidator.js +18 -8
  73. package/dist/core/validation/configValidator.js.map +1 -1
  74. package/dist/core/validation/readLogValidator.js +2 -2
  75. package/dist/core/validation/readLogValidator.js.map +1 -1
  76. package/dist/replication/nodeIdMapping.js +1 -1
  77. package/dist/replication/nodeIdMapping.js.map +1 -1
  78. package/dist/replication/replicationConnection.js +1 -4
  79. package/dist/replication/replicationConnection.js.map +1 -1
  80. package/npm-shrinkwrap.json +506 -501
  81. package/package.json +7 -4
  82. package/replication/nodeIdMapping.ts +1 -1
  83. package/replication/replicationConnection.ts +9 -4
  84. package/static/defaultConfig.yaml +3 -0
  85. package/studio/web/assets/{index-ZhLX9iRh.js → index-ClD_q6ya.js} +5 -5
  86. package/studio/web/assets/{index-ZhLX9iRh.js.map → index-ClD_q6ya.js.map} +1 -1
  87. package/studio/web/assets/{index.lazy-DzgnppiN.js → index.lazy-CXzU1gVu.js} +2 -2
  88. package/studio/web/assets/{index.lazy-DzgnppiN.js.map → index.lazy-CXzU1gVu.js.map} +1 -1
  89. package/studio/web/assets/{profile-DJ9V18dX.js → profile-DCNVg5yY.js} +2 -2
  90. package/studio/web/assets/{profile-DJ9V18dX.js.map → profile-DCNVg5yY.js.map} +1 -1
  91. package/studio/web/assets/{status-DKZUoEUd.js → status-CoGlcjSB.js} +2 -2
  92. package/studio/web/assets/{status-DKZUoEUd.js.map → status-CoGlcjSB.js.map} +1 -1
  93. package/studio/web/index.html +1 -1
@@ -20,6 +20,14 @@ import { EventEmitter } from 'node:events';
20
20
 
21
21
  type Lockdown = 'none' | 'freeze' | 'ses';
22
22
  const APPLICATIONS_LOCKDOWN: Lockdown = env.get(CONFIG_PARAMS.APPLICATIONS_LOCKDOWN);
23
+ const HARPER_MODULE_IDS = new Set([
24
+ 'harper',
25
+ 'harperdb',
26
+ 'harperdb/v1',
27
+ 'harperdb/v2',
28
+ '@harperfast/harper',
29
+ '@harperfast/harper-pro',
30
+ ]);
23
31
 
24
32
  let lockedDown = false;
25
33
  /**
@@ -138,8 +146,9 @@ function parseJsonModule(source: string, url: string): any {
138
146
  * Load a module using Node's vm.Module API with (not really secure) sandboxing
139
147
  */
140
148
  async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
141
- const moduleCache = new Map<string, SourceTextModule | SyntheticModule>();
149
+ const moduleCache = new Map<string, Promise<SourceTextModule | SyntheticModule>>();
142
150
  const linkingPromises = new Map<string, Promise<void>>();
151
+ const cjsCache = new Map<string, { exports: any }>();
143
152
 
144
153
  // Create a secure context with limited globals
145
154
  const contextObject = getGlobalObject(scope);
@@ -149,30 +158,38 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
149
158
  * Resolve module specifier to absolute URL
150
159
  */
151
160
  function resolveModule(specifier: string, referrer: string): string {
152
- if (specifier === 'harperdb' || specifier === 'harper') return 'harper';
153
- if (specifier.startsWith('harper/') || specifier.startsWith('harperdb/')) {
161
+ if (HARPER_MODULE_IDS.has(specifier)) {
162
+ return 'harper'; // resolve any harper package as an alias to a single synthetic module
163
+ }
164
+ const parts = specifier.split('/');
165
+ if (parts[0] === 'harper') {
166
+ // block harper/* for now (reserving for potential future use)
154
167
  throw new Error(`Module ${specifier} is not allowed, may only access the 'harper' module`);
155
168
  }
156
- if (specifier.startsWith('file://')) {
169
+ if (parts[0] === 'file:') {
157
170
  return specifier;
158
171
  }
159
- // For relative paths, resolve to absolute file URL
160
- if (specifier.startsWith('.')) {
161
- const resolved = createRequire(referrer).resolve(specifier);
162
- if (isAbsolute(resolved)) {
163
- return pathToFileURL(resolved).toString();
164
- }
165
- return resolved;
172
+ const resolved = createRequire(referrer).resolve(specifier);
173
+ if (isAbsolute(resolved)) {
174
+ return pathToFileURL(resolved).toString();
166
175
  }
167
- // For package names and node: specifiers, keep as-is for proper require() handling
168
- return specifier;
176
+ return resolved;
169
177
  }
170
178
 
171
179
  /**
172
180
  * Load a CommonJS module in our private context
173
181
  */
174
182
  function loadCJS(url: string, source: string): { exports: any } {
183
+ // Check cache first to handle circular dependencies
184
+ if (cjsCache.has(url)) {
185
+ return cjsCache.get(url)!;
186
+ }
187
+
188
+ // Create module object and cache it immediately (before execution)
189
+ // This allows circular dependencies to get a reference to the incomplete module
175
190
  const cjsModule = { exports: {} };
191
+ cjsCache.set(url, cjsModule);
192
+
176
193
  if (url.endsWith('.json')) {
177
194
  cjsModule.exports = parseJsonModule(source, url);
178
195
  return cjsModule;
@@ -200,7 +217,7 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
200
217
  filename: url,
201
218
  async importModuleDynamically(specifier: string, script) {
202
219
  const resolvedUrl = resolveModule(specifier, script.sourceURL);
203
- const useContainment = specifier.startsWith('.') || scope.dependencyContainment;
220
+ const useContainment = specifier.startsWith('.') || scope.dependencyContainment !== false;
204
221
  const dynamicModule = await loadModuleWithCache(resolvedUrl, useContainment);
205
222
  return dynamicModule;
206
223
  },
@@ -217,31 +234,88 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
217
234
  }
218
235
  function loadCJSModule(url: string, source: string, usePrivateGlobal: boolean): SyntheticModule {
219
236
  const cjsModule = usePrivateGlobal ? loadCJS(url, source) : { exports: require(url) };
220
- const exportNames = Object.keys(cjsModule.exports);
237
+ let exports = cjsModule.exports;
238
+ if (exports.default === undefined) {
239
+ // provide the default export for compatibility
240
+ exports = { default: exports, ...exports };
241
+ }
242
+ const exportNames = Object.keys(exports);
243
+
221
244
  const synModule = new SyntheticModule(
222
- exportNames.length > 0 ? exportNames : ['default'],
245
+ exportNames,
223
246
  function () {
224
- if (exportNames.length > 0) {
225
- for (const key of exportNames) {
226
- this.setExport(key, cjsModule.exports[key]);
227
- }
228
- } else {
229
- this.setExport('default', cjsModule.exports);
247
+ for (const key of exportNames) {
248
+ this.setExport(key, exports[key]);
230
249
  }
231
250
  },
232
251
  { identifier: url, context }
233
252
  );
234
- moduleCache.set(url, synModule);
253
+ // Don't cache here - let getOrCreateModule handle caching
235
254
  return synModule;
236
255
  }
237
256
 
257
+ /**
258
+ * Check if a package (or any of its dependencies) depends on harper
259
+ * Expects a file URL like: file:///path/to/node_modules/package-name/dist/index.js
260
+ */
261
+ function packageDependsOnHarper(fileUrl: string): boolean {
262
+ try {
263
+ // Convert file:// URL to path
264
+ const filePath = fileURLToPath(fileUrl);
265
+
266
+ // Find the node_modules directory and package name
267
+ // Example: /path/to/node_modules/package-name/dist/index.js
268
+ // or: /path/to/node_modules/@scope/package-name/dist/index.js
269
+ const nodeModulesMarker = '/node_modules/';
270
+ const nodeModulesIndex = filePath.lastIndexOf(nodeModulesMarker);
271
+ if (nodeModulesIndex === -1) return false;
272
+
273
+ // Get the part after /node_modules/
274
+ const afterNodeModules = filePath.substring(nodeModulesIndex + nodeModulesMarker.length);
275
+ const parts = afterNodeModules.split('/');
276
+
277
+ // Handle scoped packages (@scope/package-name) vs regular packages (package-name)
278
+ const beforeNodeModules = filePath.substring(0, nodeModulesIndex);
279
+ const packageRoot = parts[0].startsWith('@')
280
+ ? join(beforeNodeModules, 'node_modules', parts[0], parts[1])
281
+ : join(beforeNodeModules, 'node_modules', parts[0]);
282
+
283
+ // Read package.json from the package root
284
+ const packageJsonPath = join(packageRoot, 'package.json');
285
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
286
+
287
+ const deps = {
288
+ ...packageJson.dependencies,
289
+ ...packageJson.devDependencies,
290
+ ...packageJson.peerDependencies,
291
+ };
292
+
293
+ // Check if harper is a direct dependency
294
+ return Object.keys(deps).some((dep) => HARPER_MODULE_IDS.has(dep));
295
+ } catch {
296
+ return false;
297
+ }
298
+ }
299
+
238
300
  /**
239
301
  * Linker function for module resolution during instantiation
240
302
  */
241
303
  async function linker(specifier: string, referencingModule: SourceTextModule | SyntheticModule) {
242
304
  const resolvedUrl = resolveModule(specifier, referencingModule.identifier);
243
305
 
244
- const useContainment = specifier.startsWith('.') || scope.dependencyContainment;
306
+ // Determine if we should use VM containment for this module
307
+ let useContainment = specifier.startsWith('.'); // Always contain relative imports
308
+
309
+ if (!useContainment && scope.dependencyContainment !== false) {
310
+ // For npm packages, check if they depend on harper
311
+ if (resolvedUrl.startsWith('file://') && resolvedUrl.includes('node_modules')) {
312
+ useContainment = packageDependsOnHarper(resolvedUrl);
313
+ } else {
314
+ // Non-file URLs (bare specifiers) - use default behavior
315
+ useContainment = scope.dependencyContainment === true;
316
+ }
317
+ }
318
+
245
319
  // Return the module immediately (even if not yet linked) to support circular dependencies
246
320
  return await getOrCreateModule(resolvedUrl, useContainment);
247
321
  }
@@ -256,7 +330,7 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
256
330
  }
257
331
 
258
332
  // Create the module and cache it immediately (before linking)
259
- const module = await createModule(url, usePrivateGlobal);
333
+ const module = createModule(url, usePrivateGlobal);
260
334
  moduleCache.set(url, module);
261
335
 
262
336
  return module;
@@ -270,10 +344,11 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
270
344
 
271
345
  // Only link/evaluate once per module
272
346
  if (!linkingPromises.has(url)) {
273
- linkingPromises.set(
274
- url,
275
- module.link(linker).then(() => module.evaluate())
276
- );
347
+ const linkingPromise = (async () => {
348
+ await module.link(linker);
349
+ await module.evaluate();
350
+ })();
351
+ linkingPromises.set(url, linkingPromise);
277
352
  }
278
353
 
279
354
  // Wait for linking to complete
@@ -288,7 +363,7 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
288
363
  let module: SourceTextModule | SyntheticModule;
289
364
 
290
365
  // Handle special built-in modules
291
- if (url === 'harper' || url === 'harperdb') {
366
+ if (url === 'harper') {
292
367
  let harperExports = getHarperExports(scope);
293
368
  module = new SyntheticModule(
294
369
  Object.keys(harperExports),
@@ -299,7 +374,7 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
299
374
  },
300
375
  { identifier: url, context }
301
376
  );
302
- } else if (url.startsWith('file://')) {
377
+ } else if (url.startsWith('file://') && usePrivateGlobal) {
303
378
  checkAllowedModulePath(url, scope.verifyPath);
304
379
  let source = await readFile(new URL(url), { encoding: 'utf-8' });
305
380
 
@@ -320,67 +395,51 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
320
395
  source = await stripTypeScriptTypes(source);
321
396
  }
322
397
 
323
- // Try to parse as ESM first
398
+ // Try CJS first since it will fail fast with clear syntax errors on ESM syntax
324
399
  try {
325
- module = new SourceTextModule(source, {
326
- identifier: url,
327
- context,
328
- initializeImportMeta(meta) {
329
- meta.url = url;
330
- },
331
- async importModuleDynamically(specifier: string) {
332
- const resolvedUrl = resolveModule(specifier, url);
333
- const dynamicModule = await loadModuleWithCache(resolvedUrl, true);
334
- return dynamicModule;
335
- },
336
- });
337
- } catch (err) {
338
- // If ESM parsing fails, try to load as CommonJS
339
- if (
340
- err.message?.includes('require is not defined') ||
341
- source.includes('module.exports') ||
342
- source.includes('exports.')
343
- ) {
344
- module = loadCJSModule(url, source, usePrivateGlobal);
345
- } else {
346
- throw err;
400
+ module = loadCJSModule(url, source, usePrivateGlobal);
401
+ } catch {
402
+ // If CJS loading fails (likely due to ESM syntax like import/export), try ESM
403
+ try {
404
+ module = new SourceTextModule(source, {
405
+ identifier: url,
406
+ context,
407
+ initializeImportMeta(meta) {
408
+ meta.url = url;
409
+ },
410
+ async importModuleDynamically(specifier: string) {
411
+ const resolvedUrl = resolveModule(specifier, url);
412
+ const dynamicModule = await loadModuleWithCache(resolvedUrl, true);
413
+ return dynamicModule;
414
+ },
415
+ });
416
+ } catch (esmErr) {
417
+ // Both failed - throw the ESM error as it's likely more relevant
418
+ throw esmErr;
347
419
  }
348
420
  }
349
421
  }
350
422
  } else {
351
423
  const replacedModule = checkAllowedModulePath(url, scope.verifyPath);
352
- // For Node.js built-in modules (node:) and npm packages
424
+ // For Node.js built-in modules (node:) and npm packages without dependency containment
353
425
  // Always try require first to properly handle CJS modules with named exports
354
- try {
355
- const cjsExports = replacedModule ?? require(url);
356
- // It's a CJS module - expose all properties as named exports
357
- const exportNames = Object.keys(cjsExports);
358
- module = new SyntheticModule(
359
- exportNames.length > 0 ? [...exportNames, 'default'] : ['default'],
360
- function () {
361
- if (exportNames.length > 0) {
362
- for (const key of exportNames) {
363
- this.setExport(key, cjsExports[key]);
364
- }
365
- }
366
- this.setExport('default', cjsExports);
367
- },
368
- { identifier: url, context }
369
- );
370
- } catch {
371
- // Fall back to dynamic import for ESM packages
372
- const importedModule = await import(url);
373
- const exportNames = Object.keys(importedModule);
374
- module = new SyntheticModule(
375
- exportNames,
376
- function () {
377
- for (const key of exportNames) {
378
- this.setExport(key, importedModule[key]);
379
- }
380
- },
381
- { identifier: url, context }
382
- );
426
+ // Fall back to dynamic import for ESM packages
427
+ let importedModule = replacedModule ?? (await import(url));
428
+ const cjsModule = importedModule['module.exports'];
429
+ if (cjsModule) {
430
+ // back-compat import
431
+ importedModule = importedModule.default ? { default: importedModule.default, ...cjsModule } : cjsModule;
383
432
  }
433
+ const exportNames = Object.keys(importedModule);
434
+ module = new SyntheticModule(
435
+ exportNames,
436
+ function () {
437
+ for (const key of exportNames) {
438
+ this.setExport(key, importedModule[key]);
439
+ }
440
+ },
441
+ { identifier: url, context }
442
+ );
384
443
  }
385
444
 
386
445
  return module;
@@ -404,7 +463,15 @@ async function getCompartment(scope: ApplicationScope, globals) {
404
463
  {
405
464
  name: 'harper-app',
406
465
  resolveHook(moduleSpecifier, moduleReferrer) {
407
- if (moduleSpecifier === 'harperdb' || moduleSpecifier === 'harper') return 'harper';
466
+ if (HARPER_MODULE_IDS.has(moduleSpecifier)) {
467
+ return 'harper'; // resolve any harper package as an alias to a single synthetic module
468
+ }
469
+ const parts = moduleSpecifier.split('/');
470
+ if (parts[0] === 'harper') {
471
+ // block harper/* for now (reserving for potential future use)
472
+ throw new Error(`Module ${moduleSpecifier} is not allowed, may only access the 'harper' module`);
473
+ }
474
+
408
475
  const resolved = createRequire(moduleReferrer).resolve(moduleSpecifier);
409
476
  if (isAbsolute(resolved)) {
410
477
  const resolvedURL = pathToFileURL(resolved).toString();
@@ -523,6 +590,25 @@ function getHarperExports(scope: ApplicationScope) {
523
590
  authenticateUser: server.authenticateUser,
524
591
  operation: server.operation,
525
592
  contentTypes,
593
+ Attribute: undefined,
594
+ Config: undefined,
595
+ ConfigValue: undefined,
596
+ Context: undefined,
597
+ FileAndURLPathConfig: undefined,
598
+ FilesOption: undefined,
599
+ FilesOptionObject: undefined,
600
+ IterableEventQueue: undefined,
601
+ Logger: undefined,
602
+ Query: undefined,
603
+ RecordObject: undefined,
604
+ RequestTargetOrId: undefined,
605
+ ResourceInterface: undefined,
606
+ Scope: undefined,
607
+ Session: undefined,
608
+ SourceContext: undefined,
609
+ SubscriptionRequest: undefined,
610
+ Table: undefined,
611
+ User: undefined,
526
612
  };
527
613
  }
528
614
  const ALLOWED_NODE_BUILTIN_MODULES = env.get(CONFIG_PARAMS.APPLICATIONS_ALLOWEDBUILTINMODULES)
@@ -534,14 +620,19 @@ const ALLOWED_NODE_BUILTIN_MODULES = env.get(CONFIG_PARAMS.APPLICATIONS_ALLOWEDB
534
620
  },
535
621
  };
536
622
  const ALLOWED_COMMANDS = new Set(env.get(CONFIG_PARAMS.APPLICATIONS_ALLOWEDSPAWNCOMMANDS) ?? []);
537
- const REPLACED_BUILTIN_MODULES = {
538
- child_process: {
539
- exec: createSpawn(child_process.exec),
540
- execFile: createSpawn(child_process.execFile),
541
- fork: createSpawn(child_process.fork, true), // this is launching node, so deemed safe
542
- spawn: createSpawn(child_process.spawn),
623
+ const child_processConstrained = {
624
+ exec: createSpawn(child_process.exec),
625
+ execFile: createSpawn(child_process.execFile),
626
+ fork: createSpawn(child_process.fork, true), // this is launching node, so deemed safe
627
+ spawn: createSpawn(child_process.spawn),
628
+ execSync: function () {
629
+ throw new Error('execSync is not allowed');
543
630
  },
544
631
  };
632
+ child_processConstrained.default = child_processConstrained;
633
+ const REPLACED_BUILTIN_MODULES = {
634
+ child_process: child_processConstrained,
635
+ };
545
636
  /**
546
637
  * Creates a ChildProcess-like object for an existing process
547
638
  */
@@ -4,7 +4,7 @@ import * as harperLogger from '../utility/logging/harper_logger.js';
4
4
  import { ServerOptions } from 'http';
5
5
  import { ServerError, ClientError } from '../utility/errors/hdbError.js';
6
6
  import { Resources } from '../resources/Resources.ts';
7
- import { Resource } from '../resources/Resource.ts';
7
+ import { Resource, missingMethod, allowedMethods } from '../resources/Resource.ts';
8
8
  import { IterableEventQueue } from '../resources/IterableEventQueue.ts';
9
9
  import { transaction } from '../resources/transaction.ts';
10
10
  import { Headers, mergeHeaders } from '../server/serverHelpers/Headers.ts';
@@ -114,29 +114,38 @@ async function http(request: Context & Request, nextHandler) {
114
114
  switch (method) {
115
115
  case 'GET':
116
116
  case 'HEAD':
117
- return resource.get(target, request);
117
+ return resource.get ? resource.get(target, request) : missingMethod(resource, 'get');
118
118
  case 'POST':
119
- return resource.post(target, request.data, request);
119
+ return resource.post ? resource.post(target, request.data, request) : missingMethod(resource, 'post');
120
120
  case 'PUT':
121
- return resource.put(target, request.data, request);
121
+ return resource.put ? resource.put(target, request.data, request) : missingMethod(resource, 'put');
122
122
  case 'DELETE':
123
- return resource.delete(target, request);
123
+ return resource.delete ? resource.delete(target, request) : missingMethod(resource, 'delete');
124
124
  case 'PATCH':
125
- return resource.patch(target, request.data, request);
125
+ return resource.patch ? resource.patch(target, request.data, request) : missingMethod(resource, 'patch');
126
126
  case 'OPTIONS': // used primarily for CORS
127
- headers.setIfNone('Allow', 'GET, HEAD, POST, PUT, DELETE, PATCH, OPTIONS, TRACE, QUERY, COPY, MOVE');
127
+ headers.setIfNone(
128
+ 'Allow',
129
+ allowedMethods(resource)
130
+ .map((method) => method.toUpperCase())
131
+ .join(', ')
132
+ );
128
133
  return;
129
134
  case 'CONNECT':
130
135
  // websockets? and event-stream
131
- return resource.connect(target, null, request);
136
+ return resource.connect ? resource.connect(target, null, request) : missingMethod(resource, 'connect');
132
137
  case 'TRACE':
133
138
  return 'Harper is the terminating server';
134
139
  case 'QUERY':
135
- return resource.query(target, request.data, request);
140
+ return resource.query ? resource.query(target, request.data, request) : missingMethod(resource, 'query');
136
141
  case 'COPY': // methods suggested from webdav RFC 4918
137
- return resource.copy(target, headersObject.destination, request);
142
+ return resource.copy
143
+ ? resource.copy(target, headersObject.destination, request)
144
+ : missingMethod(resource, 'copy');
138
145
  case 'MOVE':
139
- return resource.move(target, headersObject.destination, request);
146
+ return resource.move
147
+ ? resource.move(target, headersObject.destination, request)
148
+ : missingMethod(resource, 'move');
140
149
  case 'BREW': // RFC 2324
141
150
  throw new ClientError("Harper is short and stout and can't brew coffee", 418);
142
151
  default:
@@ -8,7 +8,7 @@ import harperLogger from '../utility/logging/harper_logger.js';
8
8
  import { parentPort } from 'node:worker_threads';
9
9
  import env from '../utility/environment/environmentManager.js';
10
10
  import * as terms from '../utility/hdbTerms.ts';
11
- import { resolvePath } from '../config/configUtils.js';
11
+ import { getConfigPath } from '../config/configUtils.js';
12
12
  import { getTicketKeys } from './threads/manageThreads.js';
13
13
  import { createTLSSelector } from '../security/keys.js';
14
14
  import { createSecureServer } from 'node:http2';
@@ -181,7 +181,7 @@ function getPorts(options) {
181
181
 
182
182
  if (options?.usageType === 'operations-api' && env.get(terms.CONFIG_PARAMS.OPERATIONSAPI_NETWORK_DOMAINSOCKET)) {
183
183
  ports.push({
184
- port: resolvePath(env.get(terms.CONFIG_PARAMS.OPERATIONSAPI_NETWORK_DOMAINSOCKET)),
184
+ port: getConfigPath(terms.CONFIG_PARAMS.OPERATIONSAPI_NETWORK_DOMAINSOCKET),
185
185
  secure: false,
186
186
  });
187
187
  }
@@ -209,7 +209,7 @@ function getHTTPServer(port: number, secure: boolean, options: ServerOptions) {
209
209
  setPortServerMap(port, { protocol_name: secure ? 'HTTPS' : 'HTTP', name: getComponentName() });
210
210
  if (!httpServers[port]) {
211
211
  // TODO: These should all come from httpOptions or operationsApiOptions
212
- const serverPrefix = isOperationsServer ? 'operationsApi_network' : 'http';
212
+ const serverPrefix = isOperationsServer ? 'operationsApi_network' : (usageType ?? 'http');
213
213
  const keepAliveTimeout = env.get(serverPrefix + '_keepAliveTimeout');
214
214
  const requestTimeout = env.get(serverPrefix + '_timeout');
215
215
  const headersTimeout = env.get(serverPrefix + '_headersTimeout');
@@ -149,7 +149,7 @@ async function componentStatusRequestHandler(event) {
149
149
  if (originatorThreadId === undefined) {
150
150
  hdbLogger.debug('No originator threadId, falling back to broadcast');
151
151
  } else {
152
- hdbLogger.warn(`Failed to send direct response to thread ${originatorThreadId}, falling back to broadcast`);
152
+ hdbLogger.debug(`Failed to send direct response to thread ${originatorThreadId}, falling back to broadcast`);
153
153
  }
154
154
  await sendItcEvent(responseMessage);
155
155
  }
@@ -26,7 +26,7 @@ analytics:
26
26
  applications:
27
27
  lockdown: freeze
28
28
  containment: vm
29
- dependencyContainment: false
29
+ dependencyContainment: auto
30
30
  allowedSpawnCommands:
31
31
  - npm
32
32
  - node
@@ -426,6 +426,7 @@ export const LEGACY_CONFIG_PARAMS = {
426
426
  export const CONFIG_PARAMS = {
427
427
  ANALYTICS_AGGREGATEPERIOD: 'analytics_aggregatePeriod',
428
428
  ANALYTICS_REPLICATE: 'analytics_replicate',
429
+ ANALYTICS_STORAGEINTERVAL: 'analytics_storageInterval',
429
430
  AUTHENTICATION_AUTHORIZELOCAL: 'authentication_authorizeLocal',
430
431
  AUTHENTICATION_CACHETTL: 'authentication_cacheTTL',
431
432
  AUTHENTICATION_COOKIE_DOMAINS: 'authentication_cookie_domains',
@@ -82,7 +82,7 @@ function updateLogger(logger, logOptions, name) {
82
82
  logger.logToStdstreams = logOptions.stdStreams ?? false;
83
83
  // if there is a configured tag or if a component is logging to default/main log path, use the component name as the tag
84
84
  // to differentiate it
85
- logger.tag = logOptions.tag ?? (mainLogger.path === logger.path && name);
85
+ logger.tag = logOptions.tag ?? ((mainLogger.path === logger.path || externalLogger.path === logger.path) && name);
86
86
  }
87
87
  // creates a logger where the methods are only defined if they are within the log level.
88
88
  // Using this conditional logger means that every method call must be optional like log.trace?.('message),
@@ -98,6 +98,14 @@ function updateConditional(logger) {
98
98
  conditional.debug = LOG_LEVEL_HIERARCHY.debug >= logger.level ? logger.debug.bind(logger) : undefined;
99
99
  conditional.trace = LOG_LEVEL_HIERARCHY.trace >= logger.level ? logger.trace.bind(logger) : undefined;
100
100
  }
101
+ /**
102
+ * Resolve a config path value against rootPath if it is relative.
103
+ */
104
+ function resolveLogPath(configPath, rootPath) {
105
+ if (!configPath || !rootPath) return configPath;
106
+ if (pathModule.isAbsolute(configPath)) return configPath;
107
+ return pathModule.resolve(rootPath, configPath);
108
+ }
101
109
  async function updateLogSettings() {
102
110
  if (!rootConfig) {
103
111
  // set up the initial watcher
@@ -109,11 +117,23 @@ async function updateLogSettings() {
109
117
  }
110
118
  let rootConfigObject = rootConfig.config;
111
119
  const logOptions = rootConfigObject.logging ?? {};
120
+ // Resolve relative paths against rootPath from the same config
121
+ const rootPath = rootConfigObject.rootPath;
122
+ if (logOptions.root) {
123
+ logOptions.root = resolveLogPath(logOptions.root, rootPath);
124
+ }
125
+ if (logOptions.rotation?.path) {
126
+ logOptions.rotation.path = resolveLogPath(logOptions.rotation.path, rootPath);
127
+ }
112
128
  updateLogger(mainLogger, logOptions);
113
129
  logFilePath = mainLogger.path;
114
130
  logConsole = logOptions.console ?? false;
115
131
  if (logOptions.external) {
116
132
  updateLogger(externalLogger, logOptions.external);
133
+ for (let [name, component] of mainLogger.components) {
134
+ if (!(rootConfigObject[name] && rootConfigObject[name].logging) && component.isExternal)
135
+ updateLogger(component, logOptions.external, name);
136
+ }
117
137
  }
118
138
  for (const name in rootConfigObject) {
119
139
  // we now scan each component to see if it has logging individual configured
@@ -121,7 +141,8 @@ async function updateLogSettings() {
121
141
  if (component.logging) {
122
142
  updateLogger(mainLogger.forComponent(name), component.logging, name);
123
143
  } else if (mainLogger.hasComponent(name)) {
124
- updateLogger(mainLogger.forComponent(name), logOptions, name);
144
+ const componentLogger = mainLogger.forComponent(name);
145
+ updateLogger(componentLogger, (componentLogger.isExternal && logOptions.external) ?? logOptions, name);
125
146
  }
126
147
  }
127
148
  }
@@ -226,7 +247,7 @@ module.exports = {
226
247
  createLogger,
227
248
  logsAtLevel,
228
249
  getLogFilePath: () => logFilePath,
229
- forComponent: (name) => mainLogger.forComponent(name),
250
+ forComponent: (name, isExternal) => mainLogger.forComponent(name, isExternal),
230
251
  setMainLogger,
231
252
  setLogLevel,
232
253
  OUTPUTS,
@@ -273,6 +294,9 @@ module.exports.externalLogger = {
273
294
  loggerWithTag(tag) {
274
295
  return externalLogger.withTag(tag);
275
296
  },
297
+ forComponent(name) {
298
+ return externalLogger.forComponent(name);
299
+ },
276
300
  };
277
301
  _assignPackageExport('logger', module.exports.externalLogger);
278
302
 
@@ -467,7 +491,7 @@ function createLogger({
467
491
  component,
468
492
  }) {
469
493
  if (!logLevel) logLevel = 'info';
470
- let level = LOG_LEVEL_HIERARCHY[logLevel];
494
+ let level = typeof logLevel === 'number' ? logLevel : LOG_LEVEL_HIERARCHY[logLevel];
471
495
  let logger;
472
496
  /**
473
497
  * Log to std out and/or file
@@ -549,25 +573,29 @@ function createLogger({
549
573
  logger.logToStdstreams = logToStdstreams;
550
574
  if (!component) {
551
575
  let components = new Map();
552
- logger.forComponent = function (name) {
576
+ logger.forComponent = function (name, isExternal = false) {
553
577
  let componentLogger = components.get(name);
554
578
  if (!componentLogger) {
579
+ const protoLogger = isExternal ? externalLogger : logger;
555
580
  componentLogger = createLogger({
556
- path: logFilePath,
557
- level: logLevel,
558
- stdStreams: logToStdstreams,
559
- isExternalInstance: name === 'external',
560
- rotation,
581
+ path: protoLogger.path,
582
+ level: protoLogger.level,
583
+ stdStreams: protoLogger.logToStdstreams,
584
+ isExternalInstance: isExternal || name === 'external',
585
+ rotation: protoLogger.rotation,
561
586
  writeToLog,
562
587
  component: true,
563
588
  });
589
+ componentLogger.tag = name;
564
590
  components.set(name, componentLogger);
565
591
  }
592
+ if (isExternal) componentLogger.isExternal = true;
566
593
  return componentLogger;
567
594
  };
568
595
  logger.hasComponent = function (name) {
569
596
  return components.has(name);
570
597
  };
598
+ logger.components = components;
571
599
  }
572
600
  return logger;
573
601
  }
@@ -793,13 +821,18 @@ function getLogConfig(hdbConfigPath) {
793
821
  };
794
822
  }
795
823
  const configDoc = YAML.parseDocument(fs.readFileSync(hdbConfigPath, 'utf8'));
824
+ const rootPath = configDoc.getIn(['rootPath']);
796
825
  const level = configDoc.getIn(['logging', 'level']);
797
- const configLogPath = configDoc.getIn(['logging', 'root']);
826
+ const configLogPath = resolveLogPath(configDoc.getIn(['logging', 'root']), rootPath);
798
827
  const toFile = configDoc.getIn(['logging', 'file']);
799
828
  const toStream = configDoc.getIn(['logging', 'stdStreams']);
800
829
  const logConsole = configDoc.getIn(['logging', 'console']);
801
830
  const colorMode = configDoc.getIn(['logging', 'colors']) ?? true; // default to true
802
831
  const rotation = configDoc.getIn(['logging', 'rotation'])?.toJSON();
832
+ // Resolve rotation path if relative
833
+ if (rotation?.path) {
834
+ rotation.path = resolveLogPath(rotation.path, rootPath);
835
+ }
803
836
 
804
837
  return {
805
838
  level,