@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
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env -S node --disable-warning=ExperimentalWarning --experimental-vm-modules
1
+ #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
4
  const fs = require('node:fs');
package/core/bin/run.js CHANGED
@@ -266,7 +266,7 @@ function startupLog(portResolutions) {
266
266
  ? `, TCP: ${env.get(CONFIG_PARAMS.THREADS_DEBUG_PORT)}\n`
267
267
  : '\n';
268
268
  }
269
- const logFilePath = path.join(env.get(CONFIG_PARAMS.LOGGING_ROOT), 'hdb.log');
269
+ const logFilePath = path.join(configUtils.getConfigPath(CONFIG_PARAMS.LOGGING_ROOT), 'hdb.log');
270
270
  logMsg += `${pad('Logging:')}level: ${env.get(CONFIG_PARAMS.LOGGING_LEVEL)}, location: ${
271
271
  logFilePath + (env.get(CONFIG_PARAMS.LOGGING_STDSTREAMS) ? ', stdout/err' : '')
272
272
  }\n`;
@@ -292,7 +292,7 @@ function startupLog(portResolutions) {
292
292
  ? `enabled for ${env.get(CONFIG_PARAMS.OPERATIONSAPI_NETWORK_CORSACCESSLIST)}`
293
293
  : 'disabled'
294
294
  }`;
295
- logMsg += `, unix socket: ${env.get(CONFIG_PARAMS.OPERATIONSAPI_NETWORK_DOMAINSOCKET)}\n`;
295
+ logMsg += `, unix socket: ${configUtils.getConfigPath(CONFIG_PARAMS.OPERATIONSAPI_NETWORK_DOMAINSOCKET)}\n`;
296
296
  if (env.get(CONFIG_PARAMS.OPERATIONSAPI_NETWORK_PORT)) {
297
297
  logMsg +=
298
298
  pad('') +
@@ -1,5 +1,5 @@
1
1
  import { type Logger } from '../utility/logging/logger.ts';
2
- import { getConfigObj, getConfigValue } from '../config/configUtils.js';
2
+ import { getConfigObj, getConfigValue, getConfigPath } from '../config/configUtils.js';
3
3
  import { CONFIG_PARAMS } from '../utility/hdbTerms.js';
4
4
  import logger from '../utility/logging/harper_logger.js';
5
5
 
@@ -359,7 +359,8 @@ export async function installApplication(application: Application) {
359
359
  application.name,
360
360
  (application.packageManagerPrefix ? application.packageManagerPrefix + ' ' : '') + 'npm',
361
361
  ['install', '--force'],
362
- application.dirPath
362
+ application.dirPath,
363
+ application.install?.timeout
363
364
  );
364
365
 
365
366
  // if it succeeds, return
@@ -401,7 +402,9 @@ export class Application {
401
402
  this.payload = payload;
402
403
  this.packageIdentifier = packageIdentifier && derivePackageIdentifier(packageIdentifier);
403
404
  this.install = install;
404
- this.dirPath = join(getConfigValue(CONFIG_PARAMS.COMPONENTSROOT), name);
405
+ const componentsRoot = getConfigPath(CONFIG_PARAMS.COMPONENTSROOT);
406
+ if (!componentsRoot) throw new Error('componentsRoot is not configured');
407
+ this.dirPath = join(componentsRoot, name);
405
408
  this.logger = logger.loggerWithTag(name);
406
409
  this.packageManagerPrefix = getConfigValue(CONFIG_PARAMS.APPLICATIONS_PACKAGEMANAGERPREFIX);
407
410
  }
@@ -463,7 +466,8 @@ export async function installApplications() {
463
466
 
464
467
  const config = getConfigObj();
465
468
 
466
- const componentsRootDirPath = getConfigValue(CONFIG_PARAMS.COMPONENTSROOT);
469
+ const componentsRootDirPath = getConfigPath(CONFIG_PARAMS.COMPONENTSROOT);
470
+ if (!componentsRootDirPath) throw new Error('componentsRoot is not configured');
467
471
 
468
472
  // Ensure component directory exists
469
473
  await mkdir(componentsRootDirPath, { recursive: true });
@@ -554,7 +558,7 @@ export function nonInteractiveSpawn(
554
558
  command: string,
555
559
  args: string[],
556
560
  cwd: string,
557
- timeoutMs: number = 5 * 60 * 1000
561
+ timeoutMs: number = 60 * 60 * 1000
558
562
  ): Promise<{ stdout: string; stderr: string; code: number }> {
559
563
  return new Promise((resolve, reject) => {
560
564
  logger
@@ -1,6 +1,6 @@
1
1
  import type { Resources } from '../resources/Resources.ts';
2
2
  import { type Server } from '../server/Server.ts';
3
- import { loggerWithTag } from '../utility/logging/harper_logger.js';
3
+ import { forComponent } from '../utility/logging/harper_logger.js';
4
4
  import { scopedImport } from '../security/jsLoader.ts';
5
5
  import * as env from '../utility/environment/environmentManager.js';
6
6
  import { CONFIG_PARAMS } from '../utility/hdbTerms.ts';
@@ -23,8 +23,8 @@ export class ApplicationScope {
23
23
  dependencyContainment?: boolean; // option to set this from the scope
24
24
  verifyPath?: string;
25
25
  config: any;
26
- constructor(name: string, resources: Resources, server: Server, verifyPath?: string) {
27
- this.logger = loggerWithTag(name);
26
+ constructor(name: string, resources: Resources, server: Server, isInternal = false, verifyPath?: string) {
27
+ this.logger = forComponent(name, !isInternal);
28
28
 
29
29
  this.resources = resources;
30
30
  this.server = server;
@@ -1,10 +1,11 @@
1
+ import { onMessageByType } from '../server/threads/manageThreads.js';
1
2
  import { readdirSync, readFileSync, existsSync, realpathSync, mkdirSync, rmSync, symlinkSync } from 'node:fs';
2
3
  import { join, basename, dirname } from 'node:path';
3
4
  import { isMainThread } from 'node:worker_threads';
4
5
  import { parseDocument } from 'yaml';
5
6
  import * as env from '../utility/environment/environmentManager.js';
6
7
  import { PACKAGE_ROOT } from '../utility/packageUtils.js';
7
- import { CONFIG_PARAMS, HDB_ROOT_DIR_NAME } from '../utility/hdbTerms.ts';
8
+ import { CONFIG_PARAMS, HDB_ROOT_DIR_NAME, ITC_EVENT_TYPES } from '../utility/hdbTerms.ts';
8
9
  import * as graphqlHandler from '../resources/graphql.ts';
9
10
  import * as graphqlQueryHandler from '../server/graphqlQuerying.ts';
10
11
  import * as roles from '../resources/roles.ts';
@@ -26,7 +27,7 @@ import { getHdbBasePath } from '../utility/environment/environmentManager.js';
26
27
  import * as operationsServer from '../server/operationsServer.ts';
27
28
  import * as auth from '../security/auth.ts';
28
29
  import * as mqtt from '../server/mqtt.ts';
29
- import { getConfigObj, resolvePath } from '../config/configUtils.js';
30
+ import { getConfigObj, getConfigPath } from '../config/configUtils.js';
30
31
  import { createReuseportFd } from '../server/serverHelpers/Request.ts';
31
32
  import { ErrorResource } from '../resources/ErrorResource.ts';
32
33
  import { Scope } from './Scope.ts';
@@ -39,7 +40,7 @@ import { DEFAULT_CONFIG } from './DEFAULT_CONFIG.ts';
39
40
  import { PluginModule } from './PluginModule.ts';
40
41
  import { getEnvBuiltInComponents } from './Application.ts';
41
42
 
42
- const CF_ROUTES_DIR = resolvePath(env.get(CONFIG_PARAMS.COMPONENTSROOT));
43
+ const CF_ROUTES_DIR = getConfigPath(CONFIG_PARAMS.COMPONENTSROOT);
43
44
  let loadedComponents = new Map<any, any>();
44
45
  let watchesSetup;
45
46
  let resources;
@@ -146,20 +147,28 @@ function symlinkHarperModule(componentDirectory: string) {
146
147
  mkdirSync(nodeModulesDir);
147
148
  }
148
149
 
149
- // validate harperdb module
150
+ // validate harper module
150
151
  const harperModule = join(nodeModulesDir, 'harper');
151
152
  if (existsSync(harperModule)) {
152
- if (realpathSync(harperModule) === realpathSync(PACKAGE_ROOT)) {
153
- // if it exists and correctly linked, resolve
154
- return resolve();
153
+ if (realpathSync(harperModule) !== realpathSync(PACKAGE_ROOT)) {
154
+ // if it exists but is incorrectly linked, fix it
155
+ rmSync(harperModule, { recursive: true, force: true });
156
+ // create link to harper module
157
+ symlinkSync(PACKAGE_ROOT, harperModule, 'dir');
155
158
  }
156
-
157
- // Otherwise remove it then link
158
- rmSync(harperModule, { recursive: true, force: true });
159
+ } else {
160
+ // create link to harper module
161
+ symlinkSync(PACKAGE_ROOT, harperModule, 'dir');
162
+ }
163
+ // if there is a harperdb module, fix that too
164
+ const harperdbModule = join(nodeModulesDir, 'harperdb');
165
+ if (existsSync(harperdbModule) && realpathSync(harperdbModule) !== realpathSync(PACKAGE_ROOT)) {
166
+ // if it exists but is incorrectly linked, fix it
167
+ rmSync(harperdbModule, { recursive: true, force: true });
168
+ // create link to harper module
169
+ symlinkSync(PACKAGE_ROOT, harperdbModule, 'dir');
159
170
  }
160
171
 
161
- // create link to harperdb module
162
- symlinkSync(PACKAGE_ROOT, harperModule, 'dir');
163
172
  resolve();
164
173
  } finally {
165
174
  // finally release the lock
@@ -259,7 +268,7 @@ export async function loadComponent(
259
268
 
260
269
  const {
261
270
  providedLoadedComponents,
262
- applicationScope = new ApplicationScope(basename(componentDirectory), resources, server),
271
+ applicationScope = new ApplicationScope(basename(componentDirectory), resources, server, options.isRoot),
263
272
  isRoot,
264
273
  autoReload,
265
274
  appName,
@@ -310,7 +319,9 @@ export async function loadComponent(
310
319
  // Initialize loading status for all components (applications and extensions)
311
320
  componentLifecycle.loading(componentStatusName);
312
321
 
313
- const subApplicationScope = isRoot ? new ApplicationScope(componentName, resources, server) : applicationScope;
322
+ const subApplicationScope = isRoot
323
+ ? new ApplicationScope(componentName, resources, server, TRUSTED_RESOURCE_PLUGINS.hasOwnProperty(componentName))
324
+ : applicationScope;
314
325
 
315
326
  let extensionModule: any;
316
327
  const pkg = componentConfig.package;
@@ -332,7 +343,7 @@ export async function loadComponent(
332
343
  }
333
344
  }
334
345
  if (componentPath) {
335
- subApplicationScope.verifyPath = componentPath;
346
+ subApplicationScope.verifyPath ??= componentPath;
336
347
  if (!process.env.HARPER_SAFE_MODE) {
337
348
  extensionModule = await loadComponent(componentPath, resources, origin, {
338
349
  isRoot: false,
@@ -392,6 +403,8 @@ export async function loadComponent(
392
403
  if (resources.isWorker && extensionModule.handleApplication) {
393
404
  const scope = new Scope(appName || 'harper', componentName, componentDirectory, configPath, applicationScope);
394
405
 
406
+ onMessageByType(ITC_EVENT_TYPES.SHUTDOWN, () => scope.close());
407
+
395
408
  await sequentiallyHandleApplication(scope, extensionModule);
396
409
 
397
410
  // Mark component as loaded after successful handleApplication call
@@ -31,7 +31,7 @@ function customFunctionsStatus() {
31
31
  try {
32
32
  response = {
33
33
  port: env.get(hdbTerms.CONFIG_PARAMS.HTTP_PORT),
34
- directory: env.get(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT),
34
+ directory: configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT),
35
35
  is_enabled: true,
36
36
  };
37
37
  } catch (err) {
@@ -54,7 +54,7 @@ function customFunctionsStatus() {
54
54
  function getCustomFunctions() {
55
55
  log.trace(`getting custom api endpoints`);
56
56
  let response = {};
57
- const dir = env.get(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
57
+ const dir = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
58
58
 
59
59
  try {
60
60
  const projectFolders = fg.sync(normalize(`${dir}/*`), { onlyDirectories: true });
@@ -103,7 +103,7 @@ function getCustomFunction(req) {
103
103
  }
104
104
 
105
105
  log.trace(`getting custom api endpoint file content`);
106
- const cfDir = env.get(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
106
+ const cfDir = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
107
107
  const { project, type, file } = req;
108
108
  const fileLocation = path.join(cfDir, project, type, file + '.js');
109
109
 
@@ -141,7 +141,7 @@ async function setCustomFunction(req) {
141
141
  }
142
142
 
143
143
  log.trace(`setting custom function file content`);
144
- const cfDir = env.get(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
144
+ const cfDir = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
145
145
  const { project, type, file, function_content } = req;
146
146
 
147
147
  try {
@@ -181,7 +181,7 @@ async function dropCustomFunction(req) {
181
181
  }
182
182
 
183
183
  log.trace(`dropping custom function file`);
184
- const cfDir = env.get(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
184
+ const cfDir = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
185
185
  const { project, type, file } = req;
186
186
 
187
187
  try {
@@ -216,7 +216,7 @@ async function addComponent(req) {
216
216
  }
217
217
 
218
218
  log.trace(`adding component`);
219
- const cfDir = env.get(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
219
+ const cfDir = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
220
220
  const { project, install_command, install_timeout } = req;
221
221
 
222
222
  const template = req.template || 'https://github.com/harperdb/application-template';
@@ -264,7 +264,7 @@ async function dropCustomFunctionProject(req) {
264
264
  }
265
265
 
266
266
  log.trace(`dropping custom function project`);
267
- const cfDir = env.get(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
267
+ const cfDir = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
268
268
  const { project } = req;
269
269
 
270
270
  let apps = env.get(hdbTerms.CONFIG_PARAMS.APPS);
@@ -318,7 +318,7 @@ async function packageComponent(req) {
318
318
  throw handleHDBError(validation, validation.message, HTTP_STATUS_CODES.BAD_REQUEST);
319
319
  }
320
320
 
321
- const cfDir = env.get(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
321
+ const cfDir = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
322
322
  const { project } = req;
323
323
  log.trace(`packaging component`, project);
324
324
 
@@ -507,8 +507,8 @@ async function getComponents() {
507
507
  }
508
508
  };
509
509
 
510
- const results = await walkDir(env.get(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT), {
511
- name: env.get(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT).split(path.sep).slice(-1).pop(),
510
+ const results = await walkDir(configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT), {
511
+ name: configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT).split(path.sep).slice(-1).pop(),
512
512
  entries: [],
513
513
  });
514
514
  for (let entry of results.entries) {
@@ -557,7 +557,7 @@ async function getComponentFile(req) {
557
557
  throw handleHDBError(validation, validation.message, HTTP_STATUS_CODES.BAD_REQUEST);
558
558
  }
559
559
 
560
- const compRoot = env.get(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
560
+ const compRoot = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
561
561
  const options = req.encoding ? { encoding: req.encoding } : { encoding: 'utf8' };
562
562
 
563
563
  try {
@@ -588,7 +588,7 @@ async function setComponentFile(req) {
588
588
  }
589
589
 
590
590
  const options = req.encoding ? { encoding: req.encoding } : { encoding: 'utf8' };
591
- const pathToComp = path.join(env.get(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT), req.project, req.file);
591
+ const pathToComp = path.join(configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT), req.project, req.file);
592
592
  if (req.payload !== undefined) {
593
593
  await fs.ensureFile(pathToComp);
594
594
  await fs.outputFile(pathToComp, req.payload, options);
@@ -613,7 +613,7 @@ async function dropComponent(req) {
613
613
 
614
614
  const { project, file } = req;
615
615
  const projectPath = req.file ? path.join(project, file) : project;
616
- const pathToComponent = path.join(env.get(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT), projectPath);
616
+ const pathToComponent = path.join(configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT), projectPath);
617
617
 
618
618
  const componentSymlink = path.join(env.get(hdbTerms.CONFIG_PARAMS.ROOTPATH), 'node_modules', project);
619
619
  if (await fs.pathExists(componentSymlink)) {
@@ -4,9 +4,9 @@ const Joi = require('joi');
4
4
  const fs = require('fs-extra');
5
5
  const path = require('path');
6
6
  const validator = require('../validation/validationWrapper.js');
7
- const envMangr = require('../utility/environment/environmentManager.js');
8
7
  const hdbTerms = require('../utility/hdbTerms.ts');
9
8
  const hdbLogger = require('../utility/logging/harper_logger.js');
9
+ const configUtils = require('../config/configUtils.js');
10
10
  const { hdbErrors } = require('../utility/errors/hdbError.js');
11
11
  const { HDB_ERROR_MSGS } = hdbErrors;
12
12
 
@@ -34,7 +34,7 @@ module.exports = {
34
34
  */
35
35
  function checkProjectExists(checkExists, project, helpers) {
36
36
  try {
37
- const cfDir = envMangr.get(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
37
+ const cfDir = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
38
38
  const projectDir = path.join(cfDir, project);
39
39
 
40
40
  if (!fs.existsSync(projectDir)) {
@@ -71,7 +71,7 @@ function checkFilePath(path, helpers) {
71
71
  */
72
72
  function checkFileExists(project, type, file, helpers) {
73
73
  try {
74
- const cfDir = envMangr.get(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
74
+ const cfDir = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
75
75
  const filePath = path.join(cfDir, project, type, file + '.js');
76
76
  if (!fs.existsSync(filePath)) {
77
77
  return helpers.message(HDB_ERROR_MSGS.NO_FILE);
@@ -57,6 +57,7 @@ exports.deleteConfigFromFile = deleteConfigFromFile;
57
57
  exports.getConfigObj = getConfigObj;
58
58
  exports.resolvePath = resolvePath;
59
59
  exports.getFlatConfigObj = getFlatConfigObj;
60
+ exports.getConfigPath = getConfigPath;
60
61
 
61
62
  function resolvePath(relativePath) {
62
63
  if (relativePath?.startsWith('~/')) {
@@ -70,7 +71,23 @@ function resolvePath(relativePath) {
70
71
  return relativePath;
71
72
  }
72
73
  }
73
-
74
+ /**
75
+ * Get a config value and resolve it as a path relative to rootPath.
76
+ * Use this for any config param that represents a file/directory path.
77
+ * @param param
78
+ */
79
+ function getConfigPath(param) {
80
+ const env = require('../utility/environment/environmentManager.js');
81
+ const value = env.get(param);
82
+ if (!value || typeof value !== 'string') return value;
83
+ if (value.startsWith('~/')) {
84
+ return path.join(hdbUtils.getHomeDir(), value.slice(1));
85
+ }
86
+ if (path.isAbsolute(value)) return value;
87
+ const rootPath = env.getHdbBasePath();
88
+ if (!rootPath) return value;
89
+ return path.resolve(rootPath, value);
90
+ }
74
91
  /**
75
92
  * Builds the Harper config file using user inputs and default values from defaultConfig.yaml
76
93
  * @param args - any args that the user provided.
@@ -332,15 +349,14 @@ function initConfig(force = false) {
332
349
  * @param configFilePath
333
350
  */
334
351
  function checkForUpdatedConfig(configDoc, configFilePath) {
335
- const rootPath = configDoc.getIn(['rootPath']);
336
352
  let updateFile = false;
337
353
  if (!configDoc.hasIn(['storage', 'path'])) {
338
- configDoc.setIn(['storage', 'path'], path.join(rootPath, 'database'));
354
+ configDoc.setIn(['storage', 'path'], 'database');
339
355
  updateFile = true;
340
356
  }
341
357
 
342
358
  if (!configDoc.hasIn(['logging', 'rotation', 'path'])) {
343
- configDoc.setIn(['logging', 'rotation', 'path'], path.join(rootPath, 'log'));
359
+ configDoc.setIn(['logging', 'rotation', 'path'], 'log');
344
360
  updateFile = true;
345
361
  }
346
362
 
@@ -7,6 +7,7 @@ const path = require('path');
7
7
  const minimist = require('minimist');
8
8
  const fs = require('fs-extra');
9
9
  const _ = require('lodash');
10
+ const { getConfigPath } = require('../../../../config/configUtils.js');
10
11
  env.initSync();
11
12
 
12
13
  const { CONFIG_PARAMS, DATABASES_PARAM_CONFIG, SYSTEM_SCHEMA_NAME } = hdbTerms;
@@ -25,7 +26,7 @@ function getBaseSchemaPath() {
25
26
 
26
27
  if (env.getHdbBasePath() !== undefined) {
27
28
  BASE_SCHEMA_PATH =
28
- env.get(CONFIG_PARAMS.STORAGE_PATH) || path.join(env.getHdbBasePath(), hdbTerms.DATABASES_DIR_NAME);
29
+ getConfigPath(CONFIG_PARAMS.STORAGE_PATH) || path.join(env.getHdbBasePath(), hdbTerms.DATABASES_DIR_NAME);
29
30
  return BASE_SCHEMA_PATH;
30
31
  }
31
32
  }
@@ -52,7 +53,7 @@ function getTransactionAuditStoreBasePath() {
52
53
 
53
54
  if (env.getHdbBasePath() !== undefined) {
54
55
  TRANSACTION_STORE_PATH =
55
- env.get(hdbTerms.CONFIG_PARAMS.STORAGE_AUDIT_PATH) ||
56
+ getConfigPath(hdbTerms.CONFIG_PARAMS.STORAGE_AUDIT_PATH) ||
56
57
  path.join(env.getHdbBasePath(), hdbTerms.TRANSACTIONS_DIR_NAME);
57
58
  return TRANSACTION_STORE_PATH;
58
59
  }
@@ -683,15 +683,26 @@ function transactional(
683
683
  }
684
684
  }
685
685
  }
686
- function missingMethod(resource, method) {
687
- const error = new ClientError(`The ${resource.constructor.name} does not have a ${method} method implemented`, 405);
688
- error.allow = [];
686
+ const KNOWN_METHODS = ['get', 'head', 'put', 'post', 'delete', 'patch', 'query', 'move', 'copy'];
687
+ type ClientErrorWithMethods = ClientError & { allow: string[]; method: string };
688
+ export function missingMethod(resource: any, method: string) {
689
+ const error = new ClientError(
690
+ `The ${resource.constructor.name} does not have a ${method} method implemented`,
691
+ 405
692
+ ) as ClientErrorWithMethods;
693
+ error.allow = allowedMethods(resource);
689
694
  error.method = method;
690
- for (const method of ['get', 'put', 'post', 'delete', 'query', 'move', 'copy']) {
691
- if (typeof resource[method] === 'function') error.allow.push(method);
692
- }
693
695
  throw error;
694
696
  }
697
+
698
+ export function allowedMethods(resource: any) {
699
+ const allow = [];
700
+ for (const method of KNOWN_METHODS) {
701
+ if (typeof resource[method] === 'function') allow.push(method);
702
+ }
703
+ return allow;
704
+ }
705
+
695
706
  /**
696
707
  * This is responsible for handling a select query parameter/call that selects specific
697
708
  * properties from the returned record(s).
@@ -3,6 +3,7 @@ import { ExtendedIterable } from '@harperfast/extended-iterable';
3
3
  import { Decoder, readAuditEntry, ENTRY_DATAVIEW, AuditRecord, createAuditEntry } from './auditStore.ts';
4
4
  import { isMainThread } from 'node:worker_threads';
5
5
  import { EventEmitter } from 'node:events';
6
+ import { asBinary } from 'lmdb';
6
7
 
7
8
  if (!process.env.HARPER_NO_FLUSH_ON_EXIT && isMainThread) {
8
9
  // we want to be able to test log replay
@@ -81,7 +82,7 @@ export class RocksTransactionLogStore extends EventEmitter {
81
82
 
82
83
  putSync(suggestedKey: any, value: any, options: any) {
83
84
  if (typeof suggestedKey === 'symbol') {
84
- this.rootStore.putSync(suggestedKey, value, options);
85
+ this.rootStore.putSync(suggestedKey, asBinary(value), options);
85
86
  } else {
86
87
  this.put(suggestedKey, value, options);
87
88
  }
@@ -107,6 +108,12 @@ export class RocksTransactionLogStore extends EventEmitter {
107
108
  return this.rootStore.getSync(key);
108
109
  }
109
110
  }
111
+ getBinary(key: any) {
112
+ if (typeof key === 'number') {
113
+ throw new Error('Unsupported binary access by number');
114
+ }
115
+ return this.rootStore.getBinarySync(key);
116
+ }
110
117
  getEntry() {
111
118
  throw new Error('Not implemented');
112
119
  }
@@ -5,6 +5,7 @@ export const METRIC = {
5
5
  MAIN_THREAD_UTILIZATION: 'main-thread-utilization',
6
6
  RESOURCE_USAGE: 'resource-usage',
7
7
  UTILIZATION: 'utilization',
8
+ NODE_STORAGE: 'node-storage',
8
9
  } as const;
9
10
 
10
11
  export type BuiltInMetricName = (typeof METRIC)[keyof typeof METRIC];
@@ -131,7 +131,7 @@ export async function get(metric: string, opts?: GetAnalyticsOpts): Promise<Metr
131
131
  // remove nodeId from 'id' attr and resolve it to the actual hostname and
132
132
  // add back in as 'node' attr if selected
133
133
  const nodeId = result.id[1];
134
- result['id'] = result['id'][0];
134
+ result = { ...result, id: result.id[0] };
135
135
  if (isSelected(select, 'node')) {
136
136
  log.trace?.(`get_analytics lookup hostname for nodeId: ${nodeId}`);
137
137
  const hostname = await lookupHostname(nodeId);
@@ -3,12 +3,12 @@ import { onMessageByType } from '../../server/threads/manageThreads.js';
3
3
  import { getDatabases, table } from '../databases.ts';
4
4
  import type { Databases, Table, Tables } from '../databases.ts';
5
5
  import harperLogger from '../../utility/logging/harper_logger.js';
6
- import { stat } from 'node:fs/promises';
6
+ import { stat, readdir } from 'node:fs/promises';
7
7
  const { getLogFilePath, forComponent } = harperLogger;
8
8
  import { dirname, join } from 'path';
9
9
  import { open } from 'fs/promises';
10
10
  import { getNextMonotonicTime } from '../../utility/lmdb/commonUtility.js';
11
- import { get as envGet, initSync } from '../../utility/environment/environmentManager.js';
11
+ import { get as envGet, getHdbBasePath, initSync } from '../../utility/environment/environmentManager.js';
12
12
  import { CONFIG_PARAMS } from '../../utility/hdbTerms.ts';
13
13
  import { server } from '../../server/Server.ts';
14
14
  import * as fs from 'node:fs';
@@ -382,6 +382,43 @@ function storeVolumeMetrics(analyticsTable: Table, databases: Databases) {
382
382
  }
383
383
  }
384
384
 
385
+ export async function getDirectorySizeAsync(dirPath: string): Promise<number> {
386
+ try {
387
+ const entries = await readdir(dirPath, { withFileTypes: true });
388
+ const sizes = await Promise.all(
389
+ entries.map((entry) => {
390
+ const fullPath = join(dirPath, entry.name);
391
+ if (entry.isDirectory()) return getDirectorySizeAsync(fullPath);
392
+ if (entry.isFile()) return stat(fullPath).then((s) => s.size);
393
+ return 0;
394
+ })
395
+ );
396
+ let total = 0;
397
+ for (const size of sizes) total += size;
398
+ return total;
399
+ } catch {
400
+ // directory may not exist or be inaccessible
401
+ return 0;
402
+ }
403
+ }
404
+
405
+ const DEFAULT_STORAGE_INTERVAL = 10;
406
+ let nodeStorageInterval = DEFAULT_STORAGE_INTERVAL;
407
+ let nodeStorageCycleCount = 0;
408
+
409
+ async function storeNodeStorageMetric(analyticsTable: Table) {
410
+ if (nodeStorageInterval <= 0 || ++nodeStorageCycleCount % nodeStorageInterval !== 1) return;
411
+ try {
412
+ const size = await getDirectorySizeAsync(getHdbBasePath());
413
+ storeMetric(analyticsTable, {
414
+ metric: METRIC.NODE_STORAGE,
415
+ size,
416
+ });
417
+ } catch (error) {
418
+ log.warn?.('Error getting node storage metric', error);
419
+ }
420
+ }
421
+
385
422
  async function aggregation(fromPeriod, toPeriod = 60000) {
386
423
  const rawAnalyticsTable = getRawAnalyticsTable();
387
424
  const analyticsTable = getAnalyticsTable();
@@ -574,6 +611,9 @@ async function aggregation(fromPeriod, toPeriod = 60000) {
574
611
  // database storage volume metrics
575
612
  storeVolumeMetrics(analyticsTable, databases);
576
613
  storeVolumeMetrics(analyticsTable, { system: databases.system });
614
+
615
+ // node storage metric (total HDB directory size)
616
+ await storeNodeStorageMetric(analyticsTable);
577
617
  }
578
618
  let lastIdle = 0;
579
619
  let lastActive = 0;
@@ -654,6 +694,7 @@ if (!parentPort) onMessageByType(ANALYTICS_REPORT_TYPE, recordAnalytics);
654
694
  let scheduledTasksRunning;
655
695
  function startScheduledTasks() {
656
696
  scheduledTasksRunning = true;
697
+ nodeStorageInterval = envGet(CONFIG_PARAMS.ANALYTICS_STORAGEINTERVAL) ?? DEFAULT_STORAGE_INTERVAL;
657
698
  const AGGREGATE_PERIOD = envGet(CONFIG_PARAMS.ANALYTICS_AGGREGATEPERIOD) * 1000;
658
699
  if (AGGREGATE_PERIOD) {
659
700
  setInterval(
@@ -12,6 +12,7 @@ import {
12
12
  import { makeTable } from './Table.ts';
13
13
  import OpenEnvironmentObject from '../utility/lmdb/OpenEnvironmentObject.js';
14
14
  import { CONFIG_PARAMS, LEGACY_DATABASES_DIR_NAME, DATABASES_DIR_NAME } from '../utility/hdbTerms.ts';
15
+ import { getConfigPath } from '../config/configUtils.js';
15
16
  import { _assignPackageExport } from '../globals.js';
16
17
  import { getIndexedValues } from '../utility/lmdb/commonUtility.js';
17
18
  import * as signalling from '../utility/signalling.js';
@@ -176,7 +177,7 @@ export function getDatabases(): Databases {
176
177
  if (process.env.SCHEMAS_DATA_PATH) schemaConfigs.data = { path: process.env.SCHEMAS_DATA_PATH };
177
178
  databasePath =
178
179
  process.env.STORAGE_PATH ||
179
- envGet(CONFIG_PARAMS.STORAGE_PATH) ||
180
+ getConfigPath(CONFIG_PARAMS.STORAGE_PATH) ||
180
181
  (databasePath && (existsSync(databasePath) ? databasePath : join(getHdbBasePath(), LEGACY_DATABASES_DIR_NAME)));
181
182
  if (!databasePath) return;
182
183
 
@@ -694,7 +695,7 @@ export function database({ database: databaseName, table: tableName }) {
694
695
  tablePath ||
695
696
  databaseConfig[databaseName]?.path ||
696
697
  process.env.STORAGE_PATH ||
697
- envGet(CONFIG_PARAMS.STORAGE_PATH) ||
698
+ getConfigPath(CONFIG_PARAMS.STORAGE_PATH) ||
698
699
  (existsSync(join(hdbBasePath, DATABASES_DIR_NAME))
699
700
  ? join(hdbBasePath, DATABASES_DIR_NAME)
700
701
  : join(hdbBasePath, LEGACY_DATABASES_DIR_NAME));
@@ -734,41 +735,49 @@ export async function dropDatabase(databaseName) {
734
735
  if (!databases[databaseName]) throw new Error('Database does not exist');
735
736
  const dbTables = databases[databaseName];
736
737
  let rootStore;
738
+
737
739
  for (const tableName in dbTables) {
738
740
  const table = dbTables[tableName];
739
741
  rootStore = table.primaryStore.rootStore;
740
-
741
742
  lmdbDatabaseEnvs.delete(rootStore.path);
742
743
  rocksdbDatabaseEnvs.delete(rootStore.path);
744
+ }
745
+
746
+ for (const tableName in dbTables) {
747
+ databaseEventsEmitter.emit('dropTable', tableName, databaseName);
748
+ }
749
+
750
+ if (databaseName === 'data') {
751
+ for (const tableName in tables) {
752
+ delete tables[tableName];
753
+ }
754
+ delete tables[DEFINED_TABLES];
755
+ }
756
+ delete databases[databaseName];
743
757
 
758
+ databaseEventsEmitter.emit('dropDatabase', databaseName);
759
+
760
+ if (rootStore) {
744
761
  if (rootStore.status === 'open') {
745
762
  if (rootStore instanceof RocksDatabase) {
746
763
  rootStore.close();
747
764
  rootStore.destroy();
748
- } else if (rootStore.status === 'open') {
765
+ } else {
749
766
  await rootStore.close();
750
767
  await unlink(rootStore.path);
751
768
  }
752
769
  }
753
- databaseEventsEmitter.emit('dropTable', tableName, databaseName);
754
- }
755
- if (!rootStore) {
770
+ } else {
756
771
  rootStore = database({ database: databaseName, table: null });
757
772
  if (rootStore instanceof RocksDatabase) {
773
+ rootStore.close();
758
774
  rootStore.destroy();
759
775
  } else if (rootStore.status === 'open') {
760
776
  await rootStore.close();
761
777
  await unlink(rootStore.path);
762
778
  }
763
779
  }
764
- if (databaseName === 'data') {
765
- for (const tableName in tables) {
766
- delete tables[tableName];
767
- }
768
- delete tables[DEFINED_TABLES];
769
- }
770
- delete databases[databaseName];
771
- databaseEventsEmitter.emit('dropDatabase', databaseName);
780
+
772
781
  await deleteRootBlobPathsForDB(rootStore);
773
782
  }
774
783
  // opens an index, consulting with custom indexes that may use alternate store configuration