@harperfast/harper-pro 5.0.0-beta.6 → 5.0.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/core/bin/cliOperations.js +3 -3
  2. package/core/bin/harper.js +1 -1
  3. package/core/bin/run.js +2 -2
  4. package/core/components/Application.ts +6 -3
  5. package/core/components/ApplicationScope.ts +1 -0
  6. package/core/components/componentLoader.ts +23 -12
  7. package/core/components/operations.js +13 -13
  8. package/core/components/operationsValidation.js +3 -3
  9. package/core/config/configUtils.js +20 -4
  10. package/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +3 -2
  11. package/core/resources/DatabaseTransaction.ts +67 -32
  12. package/core/resources/Resource.ts +17 -6
  13. package/core/resources/RocksTransactionLogStore.ts +8 -1
  14. package/core/resources/Table.ts +29 -10
  15. package/core/resources/analytics/write.ts +6 -0
  16. package/core/resources/databases.ts +3 -2
  17. package/core/security/jsLoader.ts +258 -129
  18. package/core/server/REST.ts +32 -13
  19. package/core/server/http.ts +6 -3
  20. package/core/server/itc/serverHandlers.js +1 -1
  21. package/core/static/defaultConfig.yaml +1 -1
  22. package/core/utility/hdbTerms.ts +1 -0
  23. package/core/utility/logging/harper_logger.js +22 -1
  24. package/core/utility/logging/readLog.js +2 -2
  25. package/core/utility/npmUtilities.js +2 -2
  26. package/core/validation/configValidator.js +16 -8
  27. package/core/validation/readLogValidator.js +2 -2
  28. package/dist/core/bin/cliOperations.js +3 -3
  29. package/dist/core/bin/cliOperations.js.map +1 -1
  30. package/dist/core/bin/harper.js +1 -1
  31. package/dist/core/bin/run.js +2 -2
  32. package/dist/core/bin/run.js.map +1 -1
  33. package/dist/core/components/Application.js +7 -2
  34. package/dist/core/components/Application.js.map +1 -1
  35. package/dist/core/components/ApplicationScope.js +1 -0
  36. package/dist/core/components/ApplicationScope.js.map +1 -1
  37. package/dist/core/components/componentLoader.js +21 -10
  38. package/dist/core/components/componentLoader.js.map +1 -1
  39. package/dist/core/components/operations.js +13 -13
  40. package/dist/core/components/operations.js.map +1 -1
  41. package/dist/core/components/operationsValidation.js +3 -3
  42. package/dist/core/components/operationsValidation.js.map +1 -1
  43. package/dist/core/config/configUtils.js +23 -3
  44. package/dist/core/config/configUtils.js.map +1 -1
  45. package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +3 -2
  46. package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
  47. package/dist/core/resources/DatabaseTransaction.js +71 -35
  48. package/dist/core/resources/DatabaseTransaction.js.map +1 -1
  49. package/dist/core/resources/Resource.js +11 -4
  50. package/dist/core/resources/Resource.js.map +1 -1
  51. package/dist/core/resources/RocksTransactionLogStore.js +8 -1
  52. package/dist/core/resources/RocksTransactionLogStore.js.map +1 -1
  53. package/dist/core/resources/Table.js +15 -10
  54. package/dist/core/resources/Table.js.map +1 -1
  55. package/dist/core/resources/analytics/write.js +6 -0
  56. package/dist/core/resources/analytics/write.js.map +1 -1
  57. package/dist/core/resources/databases.js +3 -2
  58. package/dist/core/resources/databases.js.map +1 -1
  59. package/dist/core/security/jsLoader.js +223 -116
  60. package/dist/core/security/jsLoader.js.map +1 -1
  61. package/dist/core/server/REST.js +30 -14
  62. package/dist/core/server/REST.js.map +1 -1
  63. package/dist/core/server/http.js +6 -1
  64. package/dist/core/server/http.js.map +1 -1
  65. package/dist/core/server/itc/serverHandlers.js +1 -1
  66. package/dist/core/server/itc/serverHandlers.js.map +1 -1
  67. package/dist/core/utility/hdbTerms.js +1 -0
  68. package/dist/core/utility/hdbTerms.js.map +1 -1
  69. package/dist/core/utility/logging/harper_logger.js +24 -1
  70. package/dist/core/utility/logging/harper_logger.js.map +1 -1
  71. package/dist/core/utility/logging/readLog.js +2 -2
  72. package/dist/core/utility/logging/readLog.js.map +1 -1
  73. package/dist/core/utility/npmUtilities.js +2 -2
  74. package/dist/core/utility/npmUtilities.js.map +1 -1
  75. package/dist/core/validation/configValidator.js +18 -8
  76. package/dist/core/validation/configValidator.js.map +1 -1
  77. package/dist/core/validation/readLogValidator.js +2 -2
  78. package/dist/core/validation/readLogValidator.js.map +1 -1
  79. package/dist/licensing/usageLicensing.js +16 -26
  80. package/dist/licensing/usageLicensing.js.map +1 -1
  81. package/dist/replication/nodeIdMapping.js +1 -1
  82. package/dist/replication/nodeIdMapping.js.map +1 -1
  83. package/dist/replication/replicationConnection.js +2 -2
  84. package/dist/replication/replicationConnection.js.map +1 -1
  85. package/licensing/usageLicensing.ts +22 -32
  86. package/npm-shrinkwrap.json +615 -614
  87. package/package.json +9 -6
  88. package/replication/nodeIdMapping.ts +1 -1
  89. package/replication/replicationConnection.ts +2 -2
  90. package/static/defaultConfig.yaml +3 -0
  91. package/studio/web/assets/{index-BckVDix4.js → index-CXQsBaYq.js} +5 -5
  92. package/studio/web/assets/{index-BckVDix4.js.map → index-CXQsBaYq.js.map} +1 -1
  93. package/studio/web/assets/{index.lazy-iG1_8dzm.js → index.lazy-C3Ejfvna.js} +2 -2
  94. package/studio/web/assets/{index.lazy-iG1_8dzm.js.map → index.lazy-C3Ejfvna.js.map} +1 -1
  95. package/studio/web/assets/{profile-CzjslUXv.js → profile-BbbbWJCN.js} +2 -2
  96. package/studio/web/assets/{profile-CzjslUXv.js.map → profile-BbbbWJCN.js.map} +1 -1
  97. package/studio/web/assets/{status-BP4TQJDR.js → status-CFe85l8C.js} +2 -2
  98. package/studio/web/assets/{status-BP4TQJDR.js.map → status-CFe85l8C.js.map} +1 -1
  99. package/studio/web/index.html +1 -1
@@ -10,7 +10,7 @@ const YAML = require('yaml');
10
10
  const { packageDirectory } = require('../components/packageComponent.ts');
11
11
  const { encode } = require('cbor-x');
12
12
  const { getHdbPid } = require('../utility/processManagement/processManagement.js');
13
- const { initConfig } = require('../config/configUtils.js');
13
+ const { initConfig, getConfigPath } = require('../config/configUtils.js');
14
14
 
15
15
  const OP_ALIASES = { deploy: 'deploy_component', package: 'package_component' };
16
16
 
@@ -93,7 +93,7 @@ async function cliOperations(req) {
93
93
  process.exit(1);
94
94
  }
95
95
 
96
- if (!fs.existsSync(envMgr.get(terms.CONFIG_PARAMS.OPERATIONSAPI_NETWORK_DOMAINSOCKET))) {
96
+ if (!fs.existsSync(getConfigPath(terms.CONFIG_PARAMS.OPERATIONSAPI_NETWORK_DOMAINSOCKET))) {
97
97
  console.error('No domain socket found, unable to perform this operation');
98
98
  process.exit(1);
99
99
  }
@@ -102,7 +102,7 @@ async function cliOperations(req) {
102
102
  try {
103
103
  let options = target ?? {
104
104
  protocol: 'http:',
105
- socketPath: envMgr.get(terms.CONFIG_PARAMS.OPERATIONSAPI_NETWORK_DOMAINSOCKET),
105
+ socketPath: getConfigPath(terms.CONFIG_PARAMS.OPERATIONSAPI_NETWORK_DOMAINSOCKET),
106
106
  };
107
107
  options.method = 'POST';
108
108
  options.headers = { 'Content-Type': 'application/json' };
@@ -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
 
@@ -402,7 +402,9 @@ export class Application {
402
402
  this.payload = payload;
403
403
  this.packageIdentifier = packageIdentifier && derivePackageIdentifier(packageIdentifier);
404
404
  this.install = install;
405
- 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);
406
408
  this.logger = logger.loggerWithTag(name);
407
409
  this.packageManagerPrefix = getConfigValue(CONFIG_PARAMS.APPLICATIONS_PACKAGEMANAGERPREFIX);
408
410
  }
@@ -464,7 +466,8 @@ export async function installApplications() {
464
466
 
465
467
  const config = getConfigObj();
466
468
 
467
- const componentsRootDirPath = getConfigValue(CONFIG_PARAMS.COMPONENTSROOT);
469
+ const componentsRootDirPath = getConfigPath(CONFIG_PARAMS.COMPONENTSROOT);
470
+ if (!componentsRootDirPath) throw new Error('componentsRoot is not configured');
468
471
 
469
472
  // Ensure component directory exists
470
473
  await mkdir(componentsRootDirPath, { recursive: true });
@@ -23,6 +23,7 @@ export class ApplicationScope {
23
23
  dependencyContainment?: boolean; // option to set this from the scope
24
24
  verifyPath?: string;
25
25
  config: any;
26
+ moduleCache: any; // used by the loader to retain a cache of modules, type is an internal detail of the loader
26
27
  constructor(name: string, resources: Resources, server: Server, isInternal = false, verifyPath?: string) {
27
28
  this.logger = forComponent(name, !isInternal);
28
29
 
@@ -27,7 +27,7 @@ import { getHdbBasePath } from '../utility/environment/environmentManager.js';
27
27
  import * as operationsServer from '../server/operationsServer.ts';
28
28
  import * as auth from '../security/auth.ts';
29
29
  import * as mqtt from '../server/mqtt.ts';
30
- import { getConfigObj, resolvePath } from '../config/configUtils.js';
30
+ import { getConfigObj, getConfigPath } from '../config/configUtils.js';
31
31
  import { createReuseportFd } from '../server/serverHelpers/Request.ts';
32
32
  import { ErrorResource } from '../resources/ErrorResource.ts';
33
33
  import { Scope } from './Scope.ts';
@@ -39,8 +39,9 @@ import { lifecycle as componentLifecycle } from './status/index.ts';
39
39
  import { DEFAULT_CONFIG } from './DEFAULT_CONFIG.ts';
40
40
  import { PluginModule } from './PluginModule.ts';
41
41
  import { getEnvBuiltInComponents } from './Application.ts';
42
+ import { pathToFileURL } from 'node:url';
42
43
 
43
- const CF_ROUTES_DIR = resolvePath(env.get(CONFIG_PARAMS.COMPONENTSROOT));
44
+ const CF_ROUTES_DIR = getConfigPath(CONFIG_PARAMS.COMPONENTSROOT);
44
45
  let loadedComponents = new Map<any, any>();
45
46
  let watchesSetup;
46
47
  let resources;
@@ -147,20 +148,28 @@ function symlinkHarperModule(componentDirectory: string) {
147
148
  mkdirSync(nodeModulesDir);
148
149
  }
149
150
 
150
- // validate harperdb module
151
+ // validate harper module
151
152
  const harperModule = join(nodeModulesDir, 'harper');
152
153
  if (existsSync(harperModule)) {
153
- if (realpathSync(harperModule) === realpathSync(PACKAGE_ROOT)) {
154
- // if it exists and correctly linked, resolve
155
- return resolve();
154
+ if (realpathSync(harperModule) !== realpathSync(PACKAGE_ROOT)) {
155
+ // if it exists but is incorrectly linked, fix it
156
+ rmSync(harperModule, { recursive: true, force: true });
157
+ // create link to harper module
158
+ symlinkSync(PACKAGE_ROOT, harperModule, 'dir');
156
159
  }
157
-
158
- // Otherwise remove it then link
159
- rmSync(harperModule, { recursive: true, force: true });
160
+ } else {
161
+ // create link to harper module
162
+ symlinkSync(PACKAGE_ROOT, harperModule, 'dir');
163
+ }
164
+ // if there is a harperdb module, fix that too
165
+ const harperdbModule = join(nodeModulesDir, 'harperdb');
166
+ if (existsSync(harperdbModule) && realpathSync(harperdbModule) !== realpathSync(PACKAGE_ROOT)) {
167
+ // if it exists but is incorrectly linked, fix it
168
+ rmSync(harperdbModule, { recursive: true, force: true });
169
+ // create link to harper module
170
+ symlinkSync(PACKAGE_ROOT, harperdbModule, 'dir');
160
171
  }
161
172
 
162
- // create link to harperdb module
163
- symlinkSync(PACKAGE_ROOT, harperModule, 'dir');
164
173
  resolve();
165
174
  } finally {
166
175
  // finally release the lock
@@ -352,7 +361,9 @@ export async function loadComponent(
352
361
  const plugin = TRUSTED_RESOURCE_PLUGINS[componentName];
353
362
  extensionModule =
354
363
  typeof plugin === 'string'
355
- ? await import(plugin.startsWith('@/') ? join(PACKAGE_ROOT, plugin.slice(1)) : plugin)
364
+ ? await import(
365
+ plugin.startsWith('@/') ? pathToFileURL(join(PACKAGE_ROOT, plugin.slice(1))).toString() : plugin
366
+ )
356
367
  : plugin;
357
368
  }
358
369
 
@@ -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
  }
@@ -35,6 +35,7 @@ export type CommitOptions = {
35
35
  timestamp?: number;
36
36
  retries?: number;
37
37
  flush?: boolean;
38
+ transaction?: RocksTransaction;
38
39
  };
39
40
 
40
41
  type ReadTransaction = (LMDBTransaction | RocksTransaction) & {
@@ -116,8 +117,13 @@ export class DatabaseTransaction implements Transaction {
116
117
  if (!this.transaction) return;
117
118
  if (--this.readTxnsUsed === 0) {
118
119
  trackedTxns.delete(this);
119
- this.transaction?.abort();
120
- this.transaction = null;
120
+ if (this.open === TRANSACTION_STATE.LINGERING) {
121
+ // if we have lingering writes, we have to call commit to finish them
122
+ this.commit();
123
+ } else {
124
+ this.transaction?.abort();
125
+ this.transaction = null;
126
+ }
121
127
  }
122
128
  }
123
129
 
@@ -139,9 +145,6 @@ export class DatabaseTransaction implements Transaction {
139
145
  }
140
146
 
141
147
  addWrite(operation: TransactionWrite) {
142
- if (this.open === TRANSACTION_STATE.CLOSED) {
143
- throw new Error('Can not use a transaction that is no longer open');
144
- }
145
148
  this.writes.push(operation);
146
149
  if (!operation.deferSave) {
147
150
  // Setting saved to false means to defer saving
@@ -150,61 +153,94 @@ export class DatabaseTransaction implements Transaction {
150
153
  return operation;
151
154
  }
152
155
 
153
- save(operation: TransactionWrite, reloadEntry = false) {
156
+ save(operation: TransactionWrite, transaction?: RocksTransaction, reloadEntry = false) {
154
157
  let txnTime = this.timestamp;
155
- if (!this.transaction) {
156
- this.transaction = new RocksTransaction(this.db.store as RocksStore);
158
+ transaction ??= this.transaction;
159
+ let immediateCommit = false;
160
+ if (!transaction) {
161
+ transaction = new RocksTransaction(this.db.store as RocksStore);
162
+ if (this.open === TRANSACTION_STATE.OPEN) {
163
+ this.transaction = transaction;
164
+ } else {
165
+ // if it is closed, we have to immediately commit, using our immediate transaction
166
+ immediateCommit = true;
167
+ }
157
168
  if (txnTime) {
158
- this.transaction.setTimestamp(txnTime);
169
+ transaction.setTimestamp(txnTime);
159
170
  }
160
171
  }
161
172
  if (this.retries > 0) {
162
173
  // this is marks the rocks transaction as a retry so we don't write the transaction log again
163
- this.transaction.isRetry = true;
174
+ transaction.isRetry = true;
164
175
  }
165
- if (!txnTime) txnTime = this.timestamp = this.transaction.getTimestamp();
176
+ if (!txnTime) txnTime = this.timestamp = transaction.getTimestamp();
166
177
  if (reloadEntry || operation.entry === undefined) {
167
- operation.entry = operation.store.getEntry(operation.key, { transaction: this.transaction });
178
+ operation.entry = operation.store.getEntry(operation.key, { transaction });
179
+ }
180
+ if (!operation.saved) {
181
+ operation.saved = true;
182
+ // immediately execute in this transaction
183
+ if (operation.validate?.(txnTime) === false) {
184
+ operation.commit = () => {}; // noop if we try again
185
+ return;
186
+ }
187
+ let result: Promise<void> = operation.before?.() as Promise<void>;
188
+ if (result?.then) this.completions.push(result);
189
+ result = operation.beforeIntermediate?.() as Promise<void>;
190
+ if (result?.then) this.completions.push(result);
191
+ }
192
+ operation.commit(txnTime, operation.entry, this.retries > 0, transaction);
193
+ if (immediateCommit) {
194
+ return this.commit({ transaction }); // immediately commit if the harper transaction is closed
168
195
  }
169
- operation.saved = true;
170
- // immediately execute in this transaction
171
- if (operation.validate?.(txnTime) === false) return;
172
- let result: Promise<void> = operation.before?.() as Promise<void>;
173
- if (result?.then) this.completions.push(result);
174
- result = operation.beforeIntermediate?.() as Promise<void>;
175
- if (result?.then) this.completions.push(result);
176
- operation.commit(txnTime, operation.entry, this.retries > 0, this.transaction);
177
196
  }
178
197
 
179
198
  /**
180
199
  * Resolves with information on the timestamp and success of the commit
181
200
  */
182
201
  commit(options: CommitOptions = {}): MaybePromise<CommitResolution> {
202
+ let transaction = options.transaction ?? this.transaction; // we need to preserve this transaction as we might to resurrect it if we have to retry
183
203
  for (let i = 0; i < this.writes.length; i++) {
184
204
  let operation = this.writes[i];
185
205
  if (this.retries === 0 && operation.saved) continue;
186
- this.save(operation, i < this.validated);
206
+ this.save(operation, transaction, i < this.validated);
187
207
  }
188
208
  this.validated = this.writes.length;
189
- return when(this.completions.length > 0 ? Promise.all(this.completions) : null, () => {
209
+ const completions = this.completions;
210
+ if (completions.length > 0) this.completions = []; // reset
211
+ return when(completions.length > 0 ? Promise.all(completions) : null, () => {
212
+ if (this.writes.length > this.validated) {
213
+ // check just in case we got any more transactions while we were waiting, if so just recursively continue to finish the additional writes now
214
+ return this.commit(options);
215
+ }
216
+ this.open = TRANSACTION_STATE.CLOSED;
190
217
  let commitResolution: MaybePromise<void>;
191
218
  if (--this.readTxnsUsed > 0) {
192
219
  // we still have outstanding iterators using the transaction, we can't just commit/abort it, we will still
193
220
  // need to use it
221
+ if (this.writes.length > 0) {
222
+ // if there are outstanding writes, we have to call commit later to finish them
223
+ this.open = TRANSACTION_STATE.LINGERING;
224
+ /* TODO: This is not really the intended behavior though, we want to immediately commit writes, but continue to use
225
+ * the transaction, as there is likely existing references to the transaction in other parts of the codebase,
226
+ * particularly in the query iterator */
227
+ }
228
+ /*
194
229
  commitResolution =
195
230
  this.writes.length > 0
196
- ? this.transaction?.commit({ renewAfterCommit: true /* Try to use RocksDB's CommitAndTryCreateSnapshot */ })
197
- : // don't abort, we still have outstanding reads to complete
231
+ ? transaction?.commit({ renewAfterCommit: true }) // Try to use RocksDB's CommitAndTryCreateSnapshot
232
+ : // don't abort, we still have outstanding reads to complete
198
233
  null;
234
+ */
199
235
  } else {
200
236
  // no more reads need to be performed, just commit/abort based if there are any writes
201
237
  trackedTxns.delete(this);
202
- if (this.transaction) {
238
+ this.transaction = null; // clear transaction so any further operations operate immediately
239
+ if (transaction) {
203
240
  if (this.writes.length > 0) {
204
- commitResolution = this.transaction.commit();
241
+ commitResolution = transaction.commit();
205
242
  } else {
206
- commitResolution = this.transaction.abort();
207
- this.transaction = null; // immediately clear transaction, no need to wait
243
+ commitResolution = transaction.abort();
208
244
  }
209
245
  }
210
246
  }
@@ -220,8 +256,7 @@ export class DatabaseTransaction implements Transaction {
220
256
  const completions = [];
221
257
  return commitResolution.then(
222
258
  () => {
223
- this.transaction.onCommit?.();
224
- this.transaction = null; // the native transaction is done (reset if needed)
259
+ transaction.onCommit?.();
225
260
  if (this.next) {
226
261
  completions.push(this.next.commit(options));
227
262
  }
@@ -260,7 +295,7 @@ export class DatabaseTransaction implements Transaction {
260
295
  // if the transaction failed due to concurrent changes, we need to retry. First record this as an increased risk of contention/retry
261
296
  // for future transactions
262
297
  this.retries++;
263
- return this.commit(options); // try again
298
+ return this.commit({ transaction }); // try again
264
299
  } else throw error;
265
300
  }
266
301
  );
@@ -318,7 +353,7 @@ export class ImmediateTransaction extends DatabaseTransaction {
318
353
  save(transaction: ImmediateTransaction) {
319
354
  if (this.isCommitting) {
320
355
  // if we are in the commit, do the save and force a reload so we get a read within the transaction
321
- super.save(transaction, true);
356
+ super.save(transaction, null, true);
322
357
  } else {
323
358
  this.isCommitting = true;
324
359
  return when(this.commit(), () => {
@@ -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
  }