@harperfast/harper-pro 5.0.7 → 5.0.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 (65) hide show
  1. package/core/bin/status.js +2 -2
  2. package/core/bin/stop.js +5 -6
  3. package/core/components/OptionsWatcher.ts +9 -1
  4. package/core/dataLayer/harperBridge/TableSizeObject.ts +35 -0
  5. package/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbGetTableSize.ts +24 -0
  6. package/core/package-lock.json +6 -6
  7. package/core/resources/DatabaseTransaction.ts +8 -3
  8. package/core/resources/RecordEncoder.ts +9 -1
  9. package/core/resources/Table.ts +13 -2
  10. package/core/resources/databases.ts +2 -1
  11. package/core/resources/graphql.ts +13 -5
  12. package/core/security/jsLoader.ts +14 -2
  13. package/core/security/keys.js +1 -1
  14. package/core/server/DurableSubscriptionsSession.ts +1 -0
  15. package/core/server/serverHelpers/serverUtilities.ts +2 -5
  16. package/core/utility/environment/systemInformation.ts +698 -0
  17. package/core/utility/operation_authorization.js +2 -5
  18. package/dist/core/bin/status.js +2 -2
  19. package/dist/core/bin/status.js.map +1 -1
  20. package/dist/core/bin/stop.js +5 -5
  21. package/dist/core/bin/stop.js.map +1 -1
  22. package/dist/core/components/OptionsWatcher.js +8 -1
  23. package/dist/core/components/OptionsWatcher.js.map +1 -1
  24. package/dist/core/dataLayer/harperBridge/TableSizeObject.js +32 -0
  25. package/dist/core/dataLayer/harperBridge/TableSizeObject.js.map +1 -0
  26. package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbGetTableSize.js +18 -19
  27. package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbGetTableSize.js.map +1 -1
  28. package/dist/core/resources/DatabaseTransaction.js +6 -1
  29. package/dist/core/resources/DatabaseTransaction.js.map +1 -1
  30. package/dist/core/resources/RecordEncoder.js +10 -1
  31. package/dist/core/resources/RecordEncoder.js.map +1 -1
  32. package/dist/core/resources/Table.js +13 -2
  33. package/dist/core/resources/Table.js.map +1 -1
  34. package/dist/core/resources/databases.js +2 -1
  35. package/dist/core/resources/databases.js.map +1 -1
  36. package/dist/core/resources/graphql.js +180 -173
  37. package/dist/core/resources/graphql.js.map +1 -1
  38. package/dist/core/security/jsLoader.js +16 -2
  39. package/dist/core/security/jsLoader.js.map +1 -1
  40. package/dist/core/security/keys.js +1 -1
  41. package/dist/core/security/keys.js.map +1 -1
  42. package/dist/core/server/DurableSubscriptionsSession.js +2 -0
  43. package/dist/core/server/DurableSubscriptionsSession.js.map +1 -1
  44. package/dist/core/server/serverHelpers/serverUtilities.js +2 -2
  45. package/dist/core/server/serverHelpers/serverUtilities.js.map +1 -1
  46. package/dist/core/utility/environment/systemInformation.js +359 -219
  47. package/dist/core/utility/environment/systemInformation.js.map +1 -1
  48. package/dist/core/utility/operation_authorization.js +2 -2
  49. package/dist/core/utility/operation_authorization.js.map +1 -1
  50. package/dist/replication/replicationConnection.js +13 -6
  51. package/dist/replication/replicationConnection.js.map +1 -1
  52. package/dist/security/certificate.js +1 -1
  53. package/dist/security/certificate.js.map +1 -1
  54. package/npm-shrinkwrap.json +6 -6
  55. package/package.json +2 -2
  56. package/replication/replicationConnection.ts +24 -18
  57. package/security/certificate.ts +1 -1
  58. package/studio/web/assets/{index-C0And10y.js → index-BftP-yQ8.js} +2 -2
  59. package/studio/web/assets/{index-C0And10y.js.map → index-BftP-yQ8.js.map} +1 -1
  60. package/studio/web/index.html +1 -1
  61. package/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/TableSizeObject.js +0 -25
  62. package/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbGetTableSize.js +0 -34
  63. package/core/utility/environment/systemInformation.js +0 -355
  64. package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/TableSizeObject.js +0 -24
  65. package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/TableSizeObject.js.map +0 -1
@@ -6,7 +6,7 @@ const YAML = require('yaml');
6
6
 
7
7
  const hdbTerms = require('../utility/hdbTerms.ts');
8
8
  const hdbLog = require('../utility/logging/harper_logger.js');
9
- const sysInfo = require('../utility/environment/systemInformation.js');
9
+ const systemInformation = require('../utility/environment/systemInformation.ts');
10
10
  const envMgr = require('../utility/environment/environmentManager.js');
11
11
  const installation = require('../utility/installation.ts');
12
12
  envMgr.initSync();
@@ -51,7 +51,7 @@ async function status() {
51
51
  }
52
52
 
53
53
  // Check the saved pid against any running hdb processes
54
- const hdbSysInfo = await sysInfo.getHDBProcessInfo();
54
+ const hdbSysInfo = await systemInformation.getHDBProcessInfo();
55
55
  for (const proc of hdbSysInfo.core) {
56
56
  if (proc.pid === hdbPid) {
57
57
  status.harperdb.status = STATUSES.RUNNING;
package/core/bin/stop.js CHANGED
@@ -4,7 +4,7 @@ const hdbLogger = require('../utility/logging/harper_logger.js');
4
4
  const util = require('util');
5
5
  const childProcess = require('child_process');
6
6
  const exec = util.promisify(childProcess.exec);
7
- const sysInfo = require('../utility/environment/systemInformation.js');
7
+ const systemInformation = require('../utility/environment/systemInformation.ts');
8
8
 
9
9
  const STOP_MSG = 'Stopping Harper Pro.';
10
10
 
@@ -14,9 +14,8 @@ async function stop() {
14
14
  console.log(STOP_MSG);
15
15
  hdbLogger.notify(STOP_MSG);
16
16
 
17
- const processes = await sysInfo.getHDBProcessInfo();
18
-
19
- processes.core.forEach((p) => {
20
- exec(`kill ${p.pid}`);
21
- });
17
+ const processes = await systemInformation.getHDBProcessInfo();
18
+ for (const { pid } of processes.core) {
19
+ exec(`kill ${pid}`);
20
+ }
22
21
  }
@@ -178,7 +178,9 @@ export class OptionsWatcher extends EventEmitter<OptionsWatcherEventMap> {
178
178
  // First, ensure current and new config values are Config objects (not null, undefined, or a primitive)
179
179
  if (!this.#isConfig(currentConfigValue) || !this.#isConfig(newConfigValue)) {
180
180
  // If either is not a config, then just set as there is no need to diff/merge
181
- this.#setValue(prevKeys, newConfigValue);
181
+ if (!isDeepStrictEqual(currentConfigValue, newConfigValue)) {
182
+ this.#setValue(prevKeys, newConfigValue);
183
+ }
182
184
  return;
183
185
  }
184
186
 
@@ -256,6 +258,12 @@ export class OptionsWatcher extends EventEmitter<OptionsWatcherEventMap> {
256
258
  throw new InvalidValueTypeError(keys, value);
257
259
  }
258
260
 
261
+ if (keys.length === 0) {
262
+ this.#scopedConfig = value;
263
+ this.emit('change', keys, value, this.#scopedConfig);
264
+ return;
265
+ }
266
+
259
267
  let obj: ConfigValue = this.#scopedConfig;
260
268
 
261
269
  for (const key of keys.slice(0, -1)) {
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Represents the table size entry for a RocksDB or LMDB table.
3
+ */
4
+ export class TableSizeObject {
5
+ schema: string;
6
+ table: string;
7
+ tableSize: number;
8
+ recordCount: number;
9
+ transactionLogSize: number;
10
+ transactionLogRecordCount?: number;
11
+
12
+ /**
13
+ * @param schema - The schema of the table
14
+ * @param table - The name of the table
15
+ * @param tableSize - The data size of the table in bytes
16
+ * @param recordCount - The number of entries in the table
17
+ * @param transactionLogSize - The number of entries in the transaction log
18
+ * @param transactionLogRecordCount - The data size of the transaction log in bytes
19
+ */
20
+ constructor(
21
+ schema: string,
22
+ table: string,
23
+ tableSize: number = 0,
24
+ recordCount: number = 0,
25
+ transactionLogSize: number = 0,
26
+ transactionLogRecordCount?: number
27
+ ) {
28
+ this.schema = schema;
29
+ this.table = table;
30
+ this.tableSize = tableSize;
31
+ this.recordCount = recordCount;
32
+ this.transactionLogSize = transactionLogSize;
33
+ this.transactionLogRecordCount = transactionLogRecordCount;
34
+ }
35
+ }
@@ -0,0 +1,24 @@
1
+ import { TableSizeObject } from '../../TableSizeObject.ts';
2
+ import logger from '../../../../utility/logging/harper_logger.js';
3
+ import type { Table } from '../../../../resources/databases.ts';
4
+
5
+ /**
6
+ * Calculates the number of entries & data size in bytes for a table & its transaction log
7
+ * @param table
8
+ * @returns {TableSizeObject}
9
+ */
10
+ export function lmdbGetTableSize(table: Table) {
11
+ const tableStats = new TableSizeObject(table.databaseName, table.tableName);
12
+ try {
13
+ const dbiStat = table.primaryStore.getStats();
14
+
15
+ //get the txn log record count
16
+ const txnDbiStat = table.auditStore?.getStats();
17
+
18
+ tableStats.recordCount = dbiStat.entryCount;
19
+ tableStats.transactionLogRecordCount = txnDbiStat.entryCount;
20
+ } catch (e) {
21
+ logger.warn(`unable to stat table dbi due to ${e}`);
22
+ }
23
+ return tableStats;
24
+ }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "harper",
3
- "version": "5.0.7",
3
+ "version": "5.0.8",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "harper",
9
- "version": "5.0.7",
9
+ "version": "5.0.8",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@aws-sdk/client-s3": "^3.1012.0",
@@ -16,7 +16,7 @@
16
16
  "@fastify/autoload": "^6.3.1",
17
17
  "@fastify/compress": "^8.3.1",
18
18
  "@fastify/cors": "^11.2.0",
19
- "@fastify/static": "^9.0.0",
19
+ "@fastify/static": "^9.1.3",
20
20
  "@harperfast/extended-iterable": "^1.0.1",
21
21
  "@harperfast/rocksdb-js": "^1.0.1",
22
22
  "@turf/area": "6.5.0",
@@ -2160,9 +2160,9 @@
2160
2160
  }
2161
2161
  },
2162
2162
  "node_modules/@fastify/static": {
2163
- "version": "9.0.0",
2164
- "resolved": "https://registry.npmjs.org/@fastify/static/-/static-9.0.0.tgz",
2165
- "integrity": "sha512-r64H8Woe/vfilg5RTy7lwWlE8ZZcTrc3kebYFMEUBrMqlydhQyoiExQXdYAy2REVpST/G35+stAM8WYp1WGmMA==",
2163
+ "version": "9.1.3",
2164
+ "resolved": "https://registry.npmjs.org/@fastify/static/-/static-9.1.3.tgz",
2165
+ "integrity": "sha512-aXrYtsiryLhRxRNaxNqsn7FUISeb7rB9q4eHUPIot5aeQBLNahnz1m6thzm7JWC1poSGXS9XrX8DvuMivp2hkQ==",
2166
2166
  "funding": [
2167
2167
  {
2168
2168
  "type": "github",
@@ -256,9 +256,14 @@ export class DatabaseTransaction implements Transaction {
256
256
  if (!outstandingCommit) {
257
257
  outstandingCommit = commitResolution;
258
258
  outstandingCommitStart = performance.now();
259
- outstandingCommit.finally(() => {
260
- outstandingCommit = null;
261
- });
259
+ outstandingCommit
260
+ // if `commitResolution` rejects with and `ERR_BUSY` error, the retry logic
261
+ // will correct course, but the reject will still be propagated on the
262
+ // `outstandingCommit` promise and needs to be caught and silenced
263
+ .catch(() => {})
264
+ .finally(() => {
265
+ outstandingCommit = null;
266
+ });
262
267
  }
263
268
  const completions = [];
264
269
  return commitResolution.then(
@@ -470,6 +470,7 @@ export function handleLocalTimeForGets(store, rootStore) {
470
470
  };
471
471
  Txn.prototype.done = function () {
472
472
  done.call(this);
473
+ this.openTimer = 0; // reset so idle pool time doesn't accumulate toward the stale-open threshold
473
474
  if (this.isDone) {
474
475
  for (let i = 0; i < trackedTxns.length; i++) {
475
476
  const txn = trackedTxns[i].deref();
@@ -498,7 +499,14 @@ setInterval(() => {
498
499
  'Read transaction detected that has been open too long (over 15 minutes), ending transaction',
499
500
  txn
500
501
  );
501
- txn.done();
502
+ trackedTxns.splice(i--, 1);
503
+ txn.timerTracked = false;
504
+ txn.openTimer = 0;
505
+ try {
506
+ txn.done();
507
+ } catch (error) {
508
+ harperLogger.warn('Unexpected error force-closing stale LMDB read transaction', error);
509
+ }
502
510
  } else
503
511
  harperLogger.error(
504
512
  'Read transaction detected that has been open too long (over one minute), make sure read transactions are quickly closed',
@@ -363,6 +363,10 @@ export function makeTable(options) {
363
363
  // we listen for events by iterating through the async iterator provided by the subscription
364
364
  for await (const event of subscription) {
365
365
  try {
366
+ if (!event || typeof event !== 'object') {
367
+ logger.error?.('Bad subscription event', event);
368
+ continue;
369
+ }
366
370
  const firstWrite = event.type === 'transaction' ? event.writes[0] : event;
367
371
  if (!firstWrite) {
368
372
  logger.error?.('Bad subscription event', event);
@@ -1425,7 +1429,8 @@ export function makeTable(options) {
1425
1429
  */
1426
1430
  static evict(id, existingRecord, existingVersion) {
1427
1431
  let entry;
1428
- let transaction = txnForContext({ transaction: new DatabaseTransaction() }).getReadTxn();
1432
+ const lmdbTransaction = txnForContext({ transaction: new DatabaseTransaction() });
1433
+ let transaction = lmdbTransaction.getReadTxn();
1429
1434
  let options = { transaction };
1430
1435
  try {
1431
1436
  if (hasSourceGet || audit) {
@@ -1452,7 +1457,13 @@ export function makeTable(options) {
1452
1457
  return removeEntry(primaryStore, entry ?? primaryStore.getEntry(id), options);
1453
1458
  }
1454
1459
  } finally {
1455
- return transaction.commit();
1460
+ if (primaryStore.ifVersion) {
1461
+ // LMDB: committing the wrapper calls doneReadTxn(), removing it from trackedTxns
1462
+ return lmdbTransaction.commit();
1463
+ }
1464
+ // RocksDB: eviction writes went directly into the raw transaction via options;
1465
+ // commit it directly, as DatabaseTransaction.commit() would abort it (no tracked writes)
1466
+ return transaction?.commit();
1456
1467
  }
1457
1468
  }
1458
1469
  /**
@@ -368,7 +368,7 @@ function readRocksMetaDb(path: string, defaultTable?: string, databaseName: stri
368
368
  if (rootStore) {
369
369
  initStores(path, rootStore, databaseName, defaultTable);
370
370
  } else {
371
- rootStore = openRocksDatabase(path, { disableWAL: false }) as RocksDatabaseEx;
371
+ rootStore = openRocksDatabase(path, { disableWAL: false, enableStats: true }) as RocksDatabaseEx;
372
372
  rocksdbDatabaseEnvs.set(path, rootStore);
373
373
  initStores(path, rootStore, databaseName, defaultTable);
374
374
  replayLogs(rootStore, databases[databaseName]);
@@ -723,6 +723,7 @@ export function database({ database: databaseName, table: tableName }) {
723
723
  if (!rootStore || rootStore.status === 'closed') {
724
724
  rootStore = openRocksDatabase(path, {
725
725
  disableWAL: false,
726
+ enableStats: true,
726
727
  });
727
728
  rocksdbDatabaseEnvs.set(path, rootStore);
728
729
  }
@@ -4,6 +4,7 @@ import { table } from './databases.ts';
4
4
  import { getWorkerIndex } from '../server/threads/manageThreads.js';
5
5
  import { Resources } from './Resources.ts';
6
6
  import type { NamedTypeNode, StringValueNode } from 'graphql';
7
+ import { once } from 'node:events';
7
8
 
8
9
  const PRIMITIVE_TYPES = ['ID', 'Int', 'Float', 'Long', 'String', 'Boolean', 'Date', 'Bytes', 'Any', 'BigInt', 'Blob'];
9
10
 
@@ -35,11 +36,18 @@ server.knownGraphQLDirectives.push(
35
36
  * @param filePath
36
37
  * @param resources
37
38
  */
38
- export function start({ ensureTable }) {
39
- return {
40
- handleFile,
41
- setupFile: handleFile,
42
- };
39
+ export function handleApplication(scope: import('../components/Scope.ts').Scope) {
40
+ const entryHandler = scope.handleEntry(async (entry) => {
41
+ if (entry.eventType === 'unlink') return;
42
+ if (entry.entryType === 'directory') {
43
+ scope.logger.warn?.('graphqlSchema currently does not handle directories. Specify file patterns only.');
44
+ return;
45
+ }
46
+
47
+ await processGraphQLSchema(entry.contents, entry.urlPath, entry.absolutePath, scope.resources);
48
+ });
49
+ return once(entryHandler, 'initialLoadComplete');
50
+ }
43
51
 
44
52
  async function handleFile(gqlContent, urlPath, filePath, resources) {
45
53
  // lazy load the graphql package so we don't load it for users that don't use graphql
@@ -181,7 +181,13 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope, useC
181
181
  if (parts[0] === 'file:') {
182
182
  return specifier;
183
183
  }
184
- const resolved = createRequire(referrer).resolve(specifier);
184
+ let resolveReferrer = referrer;
185
+ if (referrer.startsWith('file:')) {
186
+ try {
187
+ resolveReferrer = pathToFileURL(realpathSync(fileURLToPath(referrer))).toString();
188
+ } catch {}
189
+ }
190
+ const resolved = createRequire(resolveReferrer).resolve(specifier);
185
191
  if (isAbsolute(resolved)) {
186
192
  return pathToFileURL(resolved).toString();
187
193
  }
@@ -206,7 +212,13 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope, useC
206
212
  cjsModule.exports = parseJsonModule(source, url);
207
213
  return cjsModule;
208
214
  }
209
- const require = createRequire(url);
215
+ let requireUrl = url;
216
+ if (url.startsWith('file://')) {
217
+ try {
218
+ requireUrl = pathToFileURL(realpathSync(fileURLToPath(url))).toString();
219
+ } catch {}
220
+ }
221
+ const require = createRequire(requireUrl);
210
222
 
211
223
  const cjsRequire = (spec: string) => {
212
224
  const resolvedUrl = resolveModule(spec, url);
@@ -857,7 +857,7 @@ function createTLSSelector(type, mtlsOptions) {
857
857
  return SNICallback;
858
858
  function SNICallback(servername, cb) {
859
859
  // find the matching server name, substituting wildcards for each part of the domain to find matches
860
- logger.info?.('TLS requested for', servername || '(no SNI)');
860
+ logger.debug?.('TLS requested for', servername || '(no SNI)');
861
861
  let matchingName = servername;
862
862
  while (true) {
863
863
  let context = secureContexts.get(matchingName);
@@ -257,6 +257,7 @@ class SubscriptionsSession {
257
257
  const _result = (async () => {
258
258
  for await (const update of subscription) {
259
259
  try {
260
+ if (!update || typeof update !== 'object') continue;
260
261
  let messageId;
261
262
  if (
262
263
  update.type &&
@@ -19,7 +19,7 @@ import restart from '../../bin/restart.js';
19
19
  import * as util from 'util';
20
20
  import insert from '../../dataLayer/insert.js';
21
21
  import globalSchema from '../../utility/globalSchema.js';
22
- import systemInformation from '../../utility/environment/systemInformation.js';
22
+ import { systemInformation } from '../../utility/environment/systemInformation.ts';
23
23
  import jobRunner from '../jobs/jobRunner.js';
24
24
  import * as tokenAuthentication from '../../security/tokenAuthentication.ts';
25
25
  import * as auth from '../../security/auth.ts';
@@ -363,10 +363,7 @@ function initializeOperationFunctionMap(): Map<OperationFunctionName, OperationF
363
363
  opFuncMap.set(terms.OPERATIONS_ENUM.RESTART, new OperationFunctionObject(restart.restart));
364
364
  opFuncMap.set(terms.OPERATIONS_ENUM.RESTART_SERVICE, new OperationFunctionObject(executeJob, restart.restartService));
365
365
  opFuncMap.set(terms.OPERATIONS_ENUM.CATCHUP, new OperationFunctionObject(catchup));
366
- opFuncMap.set(
367
- terms.OPERATIONS_ENUM.SYSTEM_INFORMATION,
368
- new OperationFunctionObject(systemInformation.systemInformation)
369
- );
366
+ opFuncMap.set(terms.OPERATIONS_ENUM.SYSTEM_INFORMATION, new OperationFunctionObject(systemInformation));
370
367
  opFuncMap.set(
371
368
  terms.OPERATIONS_ENUM.DELETE_AUDIT_LOGS_BEFORE,
372
369
  new OperationFunctionObject(executeJob, delete_.deleteAuditLogsBefore)