@harperfast/harper-pro 5.0.0-alpha.5 → 5.0.0-alpha.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 (97) hide show
  1. package/core/.github/workflows/integration-tests.yml +11 -0
  2. package/core/.github/workflows/notify-release-published.yaml +2 -2
  3. package/core/.github/workflows/unit-test.yml +2 -0
  4. package/core/bin/cliOperations.js +26 -68
  5. package/core/bin/harper.js +8 -13
  6. package/core/config/configUtils.js +11 -0
  7. package/core/integrationTests/deploy/deploy-from-github.test.ts +12 -2
  8. package/core/integrationTests/utils/harperLifecycle.ts +86 -15
  9. package/core/package.json +5 -7
  10. package/core/resources/DatabaseTransaction.ts +6 -1
  11. package/core/resources/RecordEncoder.ts +57 -3
  12. package/core/resources/RocksTransactionLogStore.ts +19 -5
  13. package/core/resources/Table.ts +103 -41
  14. package/core/resources/analytics/profile.ts +14 -7
  15. package/core/resources/auditStore.ts +28 -1
  16. package/core/security/impersonation.ts +160 -0
  17. package/core/security/role.js +9 -0
  18. package/core/server/operationsServer.ts +9 -0
  19. package/core/server/serverHelpers/serverHandlers.js +6 -1
  20. package/core/server/serverHelpers/serverUtilities.ts +1 -1
  21. package/core/unitTests/apiTests/analytics-test.mjs +1 -1
  22. package/core/unitTests/apiTests/graphql-querying-test.mjs +7 -9
  23. package/core/unitTests/apiTests/mqtt-test.mjs +2 -1
  24. package/core/unitTests/apiTests/setupTestApp.mjs +4 -5
  25. package/core/unitTests/config/configUtils.test.js +97 -0
  26. package/core/unitTests/resources/transaction.test.js +130 -0
  27. package/core/unitTests/security/impersonation.test.js +606 -0
  28. package/core/unitTests/security/role.test.js +57 -0
  29. package/core/unitTests/utility/operationPermissions.test.js +38 -1
  30. package/core/utility/hdbTerms.ts +1 -1
  31. package/core/utility/install/installer.js +18 -15
  32. package/core/utility/operationPermissions.ts +16 -0
  33. package/core/validation/role_validation.js +4 -8
  34. package/dist/core/bin/cliOperations.js +26 -68
  35. package/dist/core/bin/cliOperations.js.map +1 -1
  36. package/dist/core/bin/harper.js +8 -13
  37. package/dist/core/bin/harper.js.map +1 -1
  38. package/dist/core/config/configUtils.js +8 -0
  39. package/dist/core/config/configUtils.js.map +1 -1
  40. package/dist/core/resources/DatabaseTransaction.js +6 -1
  41. package/dist/core/resources/DatabaseTransaction.js.map +1 -1
  42. package/dist/core/resources/RecordEncoder.js +56 -4
  43. package/dist/core/resources/RecordEncoder.js.map +1 -1
  44. package/dist/core/resources/RocksTransactionLogStore.js +19 -5
  45. package/dist/core/resources/RocksTransactionLogStore.js.map +1 -1
  46. package/dist/core/resources/Table.js +94 -38
  47. package/dist/core/resources/Table.js.map +1 -1
  48. package/dist/core/resources/analytics/profile.js +13 -7
  49. package/dist/core/resources/analytics/profile.js.map +1 -1
  50. package/dist/core/resources/auditStore.js +29 -3
  51. package/dist/core/resources/auditStore.js.map +1 -1
  52. package/dist/core/security/impersonation.js +139 -0
  53. package/dist/core/security/impersonation.js.map +1 -0
  54. package/dist/core/security/role.js +8 -0
  55. package/dist/core/security/role.js.map +1 -1
  56. package/dist/core/server/operationsServer.js.map +1 -1
  57. package/dist/core/server/serverHelpers/serverHandlers.js +6 -1
  58. package/dist/core/server/serverHelpers/serverHandlers.js.map +1 -1
  59. package/dist/core/server/serverHelpers/serverUtilities.js +1 -1
  60. package/dist/core/server/serverHelpers/serverUtilities.js.map +1 -1
  61. package/dist/core/utility/hdbTerms.js +1 -1
  62. package/dist/core/utility/hdbTerms.js.map +1 -1
  63. package/dist/core/utility/install/installer.js +17 -13
  64. package/dist/core/utility/install/installer.js.map +1 -1
  65. package/dist/core/utility/operationPermissions.js +15 -0
  66. package/dist/core/utility/operationPermissions.js.map +1 -1
  67. package/dist/core/validation/role_validation.js +4 -8
  68. package/dist/core/validation/role_validation.js.map +1 -1
  69. package/dist/replication/knownNodes.js +32 -8
  70. package/dist/replication/knownNodes.js.map +1 -1
  71. package/dist/replication/replicationConnection.js +117 -31
  72. package/dist/replication/replicationConnection.js.map +1 -1
  73. package/dist/replication/replicator.js +2 -2
  74. package/dist/replication/replicator.js.map +1 -1
  75. package/dist/replication/subscriptionManager.js +1 -26
  76. package/dist/replication/subscriptionManager.js.map +1 -1
  77. package/npm-shrinkwrap.json +503 -531
  78. package/package.json +5 -5
  79. package/replication/knownNodes.ts +48 -11
  80. package/replication/replicationConnection.ts +140 -33
  81. package/replication/replicator.ts +3 -3
  82. package/replication/subscriptionManager.ts +2 -30
  83. package/studio/web/assets/{index-CT88MT9s.js → index-Cd4R_BKr.js} +2 -2
  84. package/studio/web/assets/{index-CT88MT9s.js.map → index-Cd4R_BKr.js.map} +1 -1
  85. package/studio/web/assets/{index-BdvSVLGf.js → index-Cs_ZP9GJ.js} +2 -2
  86. package/studio/web/assets/{index-BdvSVLGf.js.map → index-Cs_ZP9GJ.js.map} +1 -1
  87. package/studio/web/assets/{index-C9FGGp3i.js → index-vG4Utcyg.js} +6 -6
  88. package/studio/web/assets/{index-C9FGGp3i.js.map → index-vG4Utcyg.js.map} +1 -1
  89. package/studio/web/assets/{index.lazy-Te-1e_Et.js → index.lazy-CqinvCXH.js} +2 -2
  90. package/studio/web/assets/{index.lazy-Te-1e_Et.js.map → index.lazy-CqinvCXH.js.map} +1 -1
  91. package/studio/web/assets/{profiler-BxnKkasB.js → profiler-hYHhy-KP.js} +2 -2
  92. package/studio/web/assets/{profiler-BxnKkasB.js.map → profiler-hYHhy-KP.js.map} +1 -1
  93. package/studio/web/assets/{react-redux-B82_Ptsp.js → react-redux-zvetwa5O.js} +2 -2
  94. package/studio/web/assets/{react-redux-B82_Ptsp.js.map → react-redux-zvetwa5O.js.map} +1 -1
  95. package/studio/web/assets/{startRecording-BwtmyzVD.js → startRecording-lV1Mo15A.js} +2 -2
  96. package/studio/web/assets/{startRecording-BwtmyzVD.js.map → startRecording-lV1Mo15A.js.map} +1 -1
  97. package/studio/web/index.html +1 -1
@@ -160,5 +160,16 @@ jobs:
160
160
  name: harper-build-artifacts-node-${{ matrix.node-version }}
161
161
 
162
162
  - name: Run Integration Test Shard ${{ matrix.shard }}
163
+ env:
164
+ HARPER_INTEGRATION_TEST_LOG_DIR: /tmp/harper-integration-test-logs
163
165
  run: |
164
166
  npm run test:integration -- --shard=${{ matrix.shard }}/4
167
+
168
+ - name: Upload Harper server logs
169
+ if: failure()
170
+ uses: actions/upload-artifact@v4
171
+ with:
172
+ name: harper-server-logs-node-${{ matrix.node-version }}-shard-${{ matrix.shard }}
173
+ path: /tmp/harper-integration-test-logs/
174
+ retention-days: 3
175
+ if-no-files-found: ignore
@@ -20,13 +20,13 @@ jobs:
20
20
  payload: |
21
21
  {
22
22
  "channel": "#development-ci",
23
- "text": "Harper v${{ github.event.release.tag_name }} GitHub release published",
23
+ "text": "Harper ${{ github.event.release.tag_name }} GitHub release published",
24
24
  "blocks": [
25
25
  {
26
26
  "type": "header",
27
27
  "text": {
28
28
  "type": "plain_text",
29
- "text": "Harper v${{ github.event.release.tag_name }} release"
29
+ "text": "Harper ${{ github.event.release.tag_name }} release"
30
30
  }
31
31
  },
32
32
  {
@@ -50,6 +50,8 @@ jobs:
50
50
  HDB_ADMIN_PASSWORD: 'password'
51
51
  ROOTPATH: '/tmp/hdb'
52
52
  NODE_HOSTNAME: 'localhost'
53
+ LOGGING_LEVEL: 'info'
54
+
53
55
  run: node --enable-source-maps ./dist/bin/harper.js install
54
56
 
55
57
  - name: Run tests
@@ -12,60 +12,6 @@ const { encode } = require('cbor-x');
12
12
  const { getHdbPid } = require('../utility/processManagement/processManagement.js');
13
13
  const { initConfig } = require('../config/configUtils.js');
14
14
 
15
- const SUPPORTED_OPS = [
16
- 'describe_table',
17
- 'describe_all',
18
- 'describe_database',
19
- 'list_users',
20
- 'list_roles',
21
- 'drop_role',
22
- 'add_user',
23
- 'alter_user',
24
- 'drop_user',
25
- 'restart_service',
26
- 'restart',
27
- 'create_database',
28
- 'drop_database',
29
- 'create_table',
30
- 'drop_table',
31
- 'create_attribute',
32
- 'drop_attribute',
33
- 'search_by_id',
34
- 'insert',
35
- 'update',
36
- 'upsert',
37
- 'delete',
38
- 'search_by_value',
39
- 'csv_file_load',
40
- 'csv_url_load',
41
- 'add_component',
42
- 'deploy_component',
43
- 'package_component',
44
- 'drop_component',
45
- 'get_components',
46
- 'get_component_file',
47
- 'set_component_file',
48
- 'get_job',
49
- 'search_jobs_by_start_date',
50
- 'read_log',
51
- 'read_transaction_log',
52
- 'read_audit_log',
53
- 'delete_transaction_logs_before',
54
- 'purge_stream',
55
- 'delete_records_before',
56
- 'install_node_modules',
57
- 'set_configuration',
58
- 'get_configuration',
59
- 'create_authentication_tokens',
60
- 'refresh_operation_token',
61
- 'system_information',
62
- 'sql',
63
- 'get_status',
64
- 'set_status',
65
- 'clear_status',
66
- 'get_usage_licenses',
67
- ];
68
-
69
15
  const OP_ALIASES = { deploy: 'deploy_component', package: 'package_component' };
70
16
 
71
17
  module.exports = { cliOperations, buildRequest };
@@ -88,9 +34,7 @@ const PREPARE_OPERATION = {
88
34
  function buildRequest() {
89
35
  const req = {};
90
36
  for (const arg of process.argv.slice(2)) {
91
- if (SUPPORTED_OPS.includes(arg)) {
92
- req.operation = arg;
93
- } else if (OP_ALIASES.hasOwnProperty(arg)) {
37
+ if (OP_ALIASES.hasOwnProperty(arg)) {
94
38
  req.operation = OP_ALIASES[arg];
95
39
  } else if (arg.includes('=')) {
96
40
  let [first, ...rest] = arg.split('=');
@@ -103,6 +47,9 @@ function buildRequest() {
103
47
  }
104
48
 
105
49
  req[first] = rest;
50
+ } else {
51
+ // operation should only be in the first arg
52
+ req.operation ??= arg;
106
53
  }
107
54
  }
108
55
 
@@ -143,12 +90,12 @@ async function cliOperations(req) {
143
90
  initConfig();
144
91
  if (!getHdbPid()) {
145
92
  console.error('Harper Pro must be running to perform this operation');
146
- process.exit();
93
+ process.exit(1);
147
94
  }
148
95
 
149
96
  if (!fs.existsSync(envMgr.get(terms.CONFIG_PARAMS.OPERATIONSAPI_NETWORK_DOMAINSOCKET))) {
150
97
  console.error('No domain socket found, unable to perform this operation');
151
- process.exit();
98
+ process.exit(1);
152
99
  }
153
100
  }
154
101
  await PREPARE_OPERATION[req.operation]?.(req);
@@ -178,22 +125,33 @@ async function cliOperations(req) {
178
125
  };
179
126
  }
180
127
 
128
+ let responseLog;
181
129
  if (req.json) {
182
- console.log(JSON.stringify(responseData, null, 2));
130
+ responseLog = JSON.stringify(responseData, null, 2);
183
131
  } else {
184
- console.log(YAML.stringify(responseData).trim());
132
+ responseLog = YAML.stringify(responseData).trim();
185
133
  }
186
134
 
135
+ const { statusCode } = response;
136
+ if (statusCode < 200 || (statusCode >= 300 && statusCode !== 304)) {
137
+ const errorPrefix = responseLog.startsWith('error:') ? '' : 'error: ';
138
+ console.error(`${errorPrefix}${responseLog}`);
139
+ process.exit(1);
140
+ }
141
+
142
+ console.log(responseLog);
143
+
187
144
  return responseData;
188
145
  } catch (err) {
189
- let errMsg = 'Error: ';
190
- if (err?.response?.data?.error) {
191
- errMsg += err.response.data.error;
192
- } else if (err?.response?.data) {
193
- errMsg += err?.response?.data;
146
+ if (err.code === 'ENOENT' || err.code === 'ECONNREFUSED') {
147
+ console.error(`error: Failed to connect to Harper Pro (${err.code}): ${err.message}`);
148
+ } else if (err.code === 'EACCES') {
149
+ console.error(`error: Permission denied accessing the domain socket: ${err.message}`);
150
+ } else if (err.code === 'ENOTFOUND') {
151
+ console.error(`error: Host not found: "${err.hostname}" ${err.message}`);
194
152
  } else {
195
- return console.error(err);
153
+ console.error(`error: ${err.message ?? err}`);
196
154
  }
197
- console.error(errMsg);
155
+ process.exit(1);
198
156
  }
199
157
  }
@@ -56,14 +56,9 @@ async function harper() {
56
56
  service = process.argv[2].toLowerCase();
57
57
  }
58
58
 
59
- const cliApiOp = cliOperations.buildRequest();
60
- if (cliApiOp.operation) service = SERVICE_ACTIONS_ENUM.OPERATION;
61
-
62
59
  switch (service) {
63
- case SERVICE_ACTIONS_ENUM.OPERATION:
64
- logger.trace('calling cli operations with:', cliApiOp);
65
- await cliOperations.cliOperations(cliApiOp);
66
- return;
60
+ case SERVICE_ACTIONS_ENUM.HELP:
61
+ return HELP;
67
62
  case SERVICE_ACTIONS_ENUM.START:
68
63
  return require('./run.js').launch();
69
64
  case SERVICE_ACTIONS_ENUM.INSTALL:
@@ -121,22 +116,22 @@ async function harper() {
121
116
  console.warn(
122
117
  `It appears you are running Harper Pro in an application directory, but did not specify the path. I'll go ahead and run the application for you since that's probably what you meant. But to avoid this warning in the future, run applications in the current directory like this: "harper ${service} ."`
123
118
  );
124
- process.env.RUN_HDB_APP = '.';
119
+ process.env.RUN_HDB_APP = process.cwd();
125
120
  } else if (fs.existsSync(hdbTerms.HARPER_CONFIG_FILE) || fs.existsSync(hdbTerms.HDB_CONFIG_FILE)) {
126
121
  console.warn(
127
122
  `It appears you are running Harper Pro in a root data directory, but did not specify the path. I'll go ahead and run Harper Pro with its root path set to "." for you since that's probably what you meant. But to avoid this warning in the future, run it like this: "harper ${service} ."`
128
123
  );
129
- process.env.ROOTPATH = '.';
124
+ process.env.ROOTPATH = process.cwd();
130
125
  }
131
126
  }
132
127
  // fall through
133
128
  case undefined: // run harperdb in the foreground in standard mode
134
129
  return require('./run.js').main();
135
130
  default:
136
- console.warn(`The "${service}" command is not understood.`);
137
- // fall through
138
- case SERVICE_ACTIONS_ENUM.HELP:
139
- return HELP;
131
+ const cliApiOp = cliOperations.buildRequest();
132
+ logger.trace('calling cli operations with:', cliApiOp);
133
+ await cliOperations.cliOperations(cliApiOp);
134
+ return;
140
135
  }
141
136
  }
142
137
  exports.harper = harper;
@@ -835,6 +835,17 @@ function applyRuntimeEnvVarConfig(configDoc, configFilePath, options = {}) {
835
835
  // Apply env vars with source tracking and drift detection
836
836
  applyRuntimeEnvConfig(configObj, rootPath, options);
837
837
 
838
+ // If securePort was set to the same value as port, auto-null port to avoid clashing
839
+ if (configObj.http?.port && configObj.http?.port === configObj.http?.securePort) {
840
+ configObj.http.port = null;
841
+ }
842
+ if (
843
+ configObj.operationsApi?.network?.port &&
844
+ configObj.operationsApi?.network?.port === configObj.operationsApi?.network?.securePort
845
+ ) {
846
+ configObj.operationsApi.network.port = null;
847
+ }
848
+
838
849
  // Update the YAML document's contents
839
850
  // We update only the 'contents' property to preserve the Document instance and its methods
840
851
  const mergedDoc = YAML.parseDocument(YAML.stringify(configObj), { simpleKeys: true });
@@ -8,7 +8,6 @@ import { deepStrictEqual, ok, strictEqual } from 'node:assert/strict';
8
8
  import { setupHarper, teardownHarper, type ContextWithHarper } from '../utils/harperLifecycle.ts';
9
9
  import { join } from 'node:path';
10
10
  import { existsSync } from 'node:fs';
11
- import { setTimeout as sleep } from 'node:timers/promises';
12
11
  import { readFile } from 'node:fs/promises';
13
12
  import { parse } from 'yaml';
14
13
 
@@ -44,7 +43,18 @@ suite('GitHub application deployment', (ctx: ContextWithHarper) => {
44
43
  strictEqual(response.status, 200);
45
44
  const body = await response.json();
46
45
  deepStrictEqual(body, { message: 'Successfully deployed: test-application, restarting Harper' });
47
- await sleep(5000);
46
+ // Poll until the application API is ready (restart is async, fixed sleep is flaky)
47
+ const deadline = Date.now() + 30_000;
48
+ while (true) {
49
+ try {
50
+ const check = await fetch(`${ctx.harper.httpURL}/Greeting`);
51
+ if (check.status === 200) break;
52
+ } catch {
53
+ // server not yet accepting connections
54
+ }
55
+ if (Date.now() > deadline) throw new Error('Timed out waiting for application to be ready after restart');
56
+ await new Promise((resolve) => setTimeout(resolve, 250));
57
+ }
48
58
  ok(existsSync(join(ctx.harper.installDir, 'components', project)));
49
59
 
50
60
  // const harperAppLock = await readFile(join(ctx.harper.installDir, 'harper-application-lock.json'), 'utf-8');
@@ -1,9 +1,9 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { spawn, ChildProcess } from 'node:child_process';
3
+ import { createWriteStream, existsSync, rmSync, type WriteStream } from 'node:fs';
3
4
  import { join } from 'node:path';
4
- import { existsSync } from 'node:fs';
5
5
  import { tmpdir } from 'node:os';
6
- import { mkdtemp, rm } from 'node:fs/promises';
6
+ import { mkdtemp, mkdir, rm } from 'node:fs/promises';
7
7
  import { type SuiteContext, type TestContext } from 'node:test';
8
8
  import { getNextAvailableLoopbackAddress, releaseLoopbackAddress } from './loopbackAddressPool.ts';
9
9
 
@@ -13,6 +13,7 @@ export const OPERATIONS_API_PORT = 9925;
13
13
  export const DEFAULT_ADMIN_USERNAME = 'admin';
14
14
  export const DEFAULT_ADMIN_PASSWORD = 'Abc1234!';
15
15
  const DEFAULT_STARTUP_TIMEOUT_MS = parseInt(process.env.HARPER_INTEGRATION_TEST_STARTUP_TIMEOUT_MS, 10) || 30000;
16
+ const LOG_DIR = process.env.HARPER_INTEGRATION_TEST_LOG_DIR;
16
17
 
17
18
  /**
18
19
  * Options for setting up a Harper instance.
@@ -51,6 +52,8 @@ export interface HarperContext {
51
52
  hostname: string;
52
53
  /** Child process for the Harper instance */
53
54
  process: ChildProcess;
55
+ /** Absolute path to the log directory for this suite (only set when HARPER_INTEGRATION_TEST_LOG_DIR is configured) */
56
+ logDir?: string;
54
57
  }
55
58
 
56
59
  /**
@@ -81,27 +84,63 @@ function getHarperScript(): string {
81
84
  return harperScript;
82
85
  }
83
86
 
87
+ /**
88
+ * Sanitizes a string for use as a filesystem directory name.
89
+ */
90
+ function sanitizeForFilesystem(name: string): string {
91
+ return name
92
+ .replace(/[^a-zA-Z0-9_-]/g, '_')
93
+ .replace(/_+/g, '_')
94
+ .substring(0, 100);
95
+ }
96
+
97
+ interface RunHarperCommandOptions {
98
+ args: string[];
99
+ env: any;
100
+ completionMessage?: string;
101
+ /** When set, stdout and stderr are written to files in this directory */
102
+ logDir?: string;
103
+ }
104
+
84
105
  /**
85
106
  * Runs a Harper CLI command and captures output.
86
107
  *
87
- * @param args - Additional arguments to pass to the command
108
+ * When `logDir` is provided, stdout and stderr are also written to files
109
+ * (`stdout.log` and `stderr.log`) in that directory.
110
+ *
88
111
  * @throws {AssertionError} If the command exits with a non-zero status code
89
112
  */
90
- function runHarperCommand(args: string[], env: any, completionMessage?: string): Promise<ChildProcess> {
113
+ function runHarperCommand({ args, env, completionMessage, logDir }: RunHarperCommandOptions): Promise<ChildProcess> {
91
114
  const harperScript = getHarperScript();
92
115
  const proc = spawn('node', ['--trace-warnings', harperScript, ...args], {
93
116
  env: { ...process.env, ...env },
94
117
  });
118
+
119
+ let stdoutStream: WriteStream | undefined;
120
+ let stderrStream: WriteStream | undefined;
121
+ if (logDir) {
122
+ stdoutStream = createWriteStream(join(logDir, 'stdout.log'));
123
+ stderrStream = createWriteStream(join(logDir, 'stderr.log'));
124
+ }
125
+
95
126
  return new Promise((resolve, reject) => {
96
127
  let stdout = '';
97
128
  let stderr = '';
98
129
  let timer = setTimeout(() => {
99
- reject(`Harper process timed out after ${DEFAULT_STARTUP_TIMEOUT_MS}ms`);
130
+ let errorMessage = `Harper process timed out after ${DEFAULT_STARTUP_TIMEOUT_MS}ms`;
131
+ if (stdout) {
132
+ errorMessage += `\n\nstdout:\n${stdout}`;
133
+ }
134
+ if (stderr) {
135
+ errorMessage += `\n\nstderr:\n${stderr}`;
136
+ }
137
+ reject(errorMessage);
100
138
  proc.kill();
101
139
  }, DEFAULT_STARTUP_TIMEOUT_MS);
102
140
 
103
141
  proc.stdout?.on('data', (data: Buffer) => {
104
142
  const dataString = data.toString();
143
+ stdoutStream?.write(data);
105
144
  if (completionMessage && dataString.includes(completionMessage)) {
106
145
  clearTimeout(timer);
107
146
  resolve(proc);
@@ -110,8 +149,15 @@ function runHarperCommand(args: string[], env: any, completionMessage?: string):
110
149
  });
111
150
 
112
151
  proc.stderr?.on('data', (data: Buffer) => {
152
+ stderrStream?.write(data);
113
153
  stderr += data.toString();
114
154
  });
155
+
156
+ proc.on('exit', () => {
157
+ stdoutStream?.end();
158
+ stderrStream?.end();
159
+ });
160
+
115
161
  proc.on('error', (error) => {
116
162
  reject(error);
117
163
  });
@@ -179,9 +225,33 @@ export async function startHarper(ctx: ContextWithHarper, options?: SetupHarperO
179
225
  );
180
226
  const installDir = ctx.harper?.installDir ?? (await mkdtemp(installDirPrefix));
181
227
 
182
- const loopbackAddress = ctx.hostname ?? (await getNextAvailableLoopbackAddress());
183
- const harperProcess = await runHarperCommand(
184
- [
228
+ const loopbackAddress = ctx.harper?.hostname ?? (await getNextAvailableLoopbackAddress());
229
+
230
+ // Set up per-suite log directory when HARPER_INTEGRATION_TEST_LOG_DIR is configured
231
+ let logDir: string | undefined;
232
+ if (LOG_DIR) {
233
+ const suiteName = sanitizeForFilesystem(ctx.name || 'unknown');
234
+ logDir = join(LOG_DIR, `${suiteName}-${sanitizeForFilesystem(loopbackAddress)}`);
235
+ await mkdir(logDir, { recursive: true });
236
+ }
237
+
238
+ // Point Harper's log directory to the suite log dir so hdb.log is preserved for upload
239
+ const config = { ...(options?.config || {}) };
240
+ if (logDir) {
241
+ config.logging = { ...config.logging, root: logDir };
242
+
243
+ // Clean up log directory on successful exit — only keep logs when tests fail
244
+ process.on('exit', (code) => {
245
+ if (code === 0) {
246
+ try {
247
+ rmSync(logDir, { recursive: true, force: true });
248
+ } catch {}
249
+ }
250
+ });
251
+ }
252
+
253
+ const harperProcess = await runHarperCommand({
254
+ args: [
185
255
  `--ROOTPATH=${installDir}`,
186
256
  '--DEFAULTS_MODE=dev',
187
257
  `--HDB_ADMIN_USERNAME=${DEFAULT_ADMIN_USERNAME}`,
@@ -192,11 +262,12 @@ export async function startHarper(ctx: ContextWithHarper, options?: SetupHarperO
192
262
  `--HTTP_PORT=${loopbackAddress}:${HTTP_PORT}`,
193
263
  `--OPERATIONSAPI_NETWORK_PORT=${loopbackAddress}:${OPERATIONS_API_PORT}`,
194
264
  '--LOGGING_LEVEL=debug',
195
- '--HARPER_SET_CONFIG=' + JSON.stringify(options?.config || {}),
265
+ '--HARPER_SET_CONFIG=' + JSON.stringify(config),
196
266
  ],
197
- options?.env || {},
198
- 'successfully started'
199
- );
267
+ env: options?.env || {},
268
+ completionMessage: 'successfully started',
269
+ logDir,
270
+ });
200
271
 
201
272
  ctx.harper = {
202
273
  installDir,
@@ -208,6 +279,7 @@ export async function startHarper(ctx: ContextWithHarper, options?: SetupHarperO
208
279
  operationsAPIURL: `http://${loopbackAddress}:${OPERATIONS_API_PORT}`,
209
280
  hostname: loopbackAddress,
210
281
  process: harperProcess,
282
+ logDir,
211
283
  };
212
284
 
213
285
  return ctx;
@@ -218,7 +290,6 @@ export async function startHarper(ctx: ContextWithHarper, options?: SetupHarperO
218
290
  *
219
291
  * This function stops the Harper instance, releases the loopback address,
220
292
  * and removes the installation directory.
221
- *
222
293
  * @param ctx - The test context with Harper instance details
223
294
  *
224
295
  * @example
@@ -235,7 +306,7 @@ export async function startHarper(ctx: ContextWithHarper, options?: SetupHarperO
235
306
  * ```
236
307
  */
237
308
  export async function teardownHarper(ctx: ContextWithHarper): Promise<void> {
238
- await new Promise((resolve) => {
309
+ await new Promise<void>((resolve) => {
239
310
  let timer: NodeJS.Timeout;
240
311
  ctx.harper.process.on('exit', () => {
241
312
  resolve();
@@ -246,7 +317,7 @@ export async function teardownHarper(ctx: ContextWithHarper): Promise<void> {
246
317
  try {
247
318
  ctx.harper.process.kill('SIGKILL');
248
319
  } catch {
249
- // possible that the process terminated but the exit event hasn't reached us yet
320
+ // possible that the process terminated but the exit event hasn't fired yet
250
321
  }
251
322
  resolve();
252
323
  }, 200);
package/core/package.json CHANGED
@@ -95,9 +95,7 @@
95
95
  }
96
96
  },
97
97
  "exports": {
98
- ".": "./index.js",
99
- "./v1": "./v1.js",
100
- "./v2": "./v2.js"
98
+ ".": "./dist/index.js"
101
99
  },
102
100
  "devDependencies": {
103
101
  "@harperdb/code-guidelines": "^0.0.6",
@@ -140,7 +138,7 @@
140
138
  "@fastify/cors": "~9.0.1",
141
139
  "@fastify/static": "~7.0.4",
142
140
  "@harperfast/extended-iterable": "^1.0.1",
143
- "@harperfast/rocksdb-js": "^0.1.10",
141
+ "@harperfast/rocksdb-js": "^0.1.11",
144
142
  "@turf/area": "6.5.0",
145
143
  "@turf/boolean-contains": "6.5.0",
146
144
  "@turf/boolean-disjoint": "6.5.0",
@@ -153,7 +151,7 @@
153
151
  "alasql": "4.6.6",
154
152
  "argon2": "0.44.0",
155
153
  "asn1js": "3.0.7",
156
- "cbor-x": "1.6.0",
154
+ "cbor-x": "1.6.4",
157
155
  "chalk": "4.1.2",
158
156
  "chokidar": "^4.0.3",
159
157
  "cli-progress": "3.12.0",
@@ -182,14 +180,14 @@
182
180
  "minimist": "1.2.8",
183
181
  "moment": "2.30.1",
184
182
  "mqtt-packet": "~9.0.1",
185
- "msgpackr": "1.11.8",
183
+ "msgpackr": "1.11.9",
186
184
  "needle": "3.3.1",
187
185
  "node-forge": "^1.3.1",
188
186
  "node-stream-zip": "1.15.0",
189
187
  "node-unix-socket": "0.2.7",
190
188
  "normalize-path": "^3.0.0",
191
189
  "ora": "8.2.0",
192
- "ordered-binary": "1.5.3",
190
+ "ordered-binary": "1.6.1",
193
191
  "papaparse": "5.5.3",
194
192
  "passport": "0.7.0",
195
193
  "passport-http": "0.3.0",
@@ -351,7 +351,12 @@ function startMonitoringTxns() {
351
351
  );
352
352
  // reset the transaction
353
353
  try {
354
- txn.commit();
354
+ const result = txn.commit();
355
+ if (result?.then) {
356
+ result.catch((error) => {
357
+ harperLogger.debug?.(`Error committing timed out transaction: ${error.message}`);
358
+ });
359
+ }
355
360
  } catch (error) {
356
361
  harperLogger.debug?.(`Error committing timed out transaction: ${error.message}`);
357
362
  }