Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b81e8ac
Run timer with a default after profile so it can be dynamically chang…
kriszyp Oct 11, 2025
5ce80c2
Only restart the profiler if a delay is provided
kriszyp Oct 13, 2025
2ba254e
Update 12_configuration.mjs
mihaiharperdb Oct 13, 2025
50524b3
Properly tolerate unknown directive
kriszyp Oct 16, 2025
057d778
Fix using post() with new resource API
kriszyp Oct 16, 2025
21bb742
Delete unused duplicate autoCastBoolean
cap10morgan Oct 16, 2025
49f358c
Cast bool strings case insensitively
cap10morgan Oct 16, 2025
a57456c
Skip deleted records when doing a database copy/compact
kriszyp Oct 17, 2025
f6a245e
Tolerate no audit record (address unit test failure)
kriszyp Oct 17, 2025
86e0f54
fix: Windows exports have a backslash
ldt1996 Oct 18, 2025
a95714f
fix: check if connections exist before closing them
ldt1996 Oct 19, 2025
90f14e6
Update server/threads/threadServer.js
ldt1996 Oct 19, 2025
8916b3f
fix: check if connections exist before closing them
ldt1996 Oct 19, 2025
1d74fbb
Update server/threads/threadServer.js
ldt1996 Oct 19, 2025
ad7021a
Turn off audit append flag to see if it is related to corruption
kriszyp Oct 20, 2025
ac9ce8f
Tighten the constraints on the blob cleanup set size to avoid too lar…
kriszyp Oct 20, 2025
d57b810
Make sure to pass in indexNulls flag on database startup for consiste…
kriszyp Oct 21, 2025
9a2f55b
Add option to debug long running transactions
kriszyp Oct 18, 2025
18e1105
initial config env vars (default and set/override)
heskew Oct 13, 2025
6414392
removing a set config should resore file/default value if it exists
heskew Oct 18, 2025
deda047
handle the scenario where a previously set SET_CONFIG is removed - re…
heskew Oct 20, 2025
660a7e2
add a bit more info about the config state tracking
heskew Oct 20, 2025
78787dd
include file extensions on imports
heskew Oct 21, 2025
b7b2b28
adding some error handling around env config processing and yaml parsing
heskew Oct 21, 2025
46ae3a0
If npm pack fails, return the error message
kriszyp Oct 21, 2025
9678100
Rename read_log's until attr to to
cap10morgan Oct 16, 2025
1fc9e0b
fix missing urlPath option on initial entry handler creation (#2944)
Ethan-Arrowood Oct 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions bin/copyDb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@
const sourceDbisDb = rootStore.dbisDb;
const sourceAuditStore = rootStore.auditStore;
const targetEnv = open(new OpenEnvironmentObject(targetDatabasePath));
const targetDbisDb = targetEnv.openDB(INTERNAL_DBIS_NAME);

Check failure on line 165 in bin/copyDb.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v20)

Argument of type 'string' is not assignable to parameter of type 'DatabaseOptions & { name: string; }'.

Check failure on line 165 in bin/copyDb.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v24)

Argument of type 'string' is not assignable to parameter of type 'DatabaseOptions & { name: string; }'.

Check failure on line 165 in bin/copyDb.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v22)

Argument of type 'string' is not assignable to parameter of type 'DatabaseOptions & { name: string; }'.
let written;
let outstandingWrites = 0;
// we use a single transaction to get a snapshot, also we can't use snapshot: false on dupsort dbs
Expand All @@ -188,15 +188,15 @@
// we want to directly copy bytes so we don't have the overhead of
// encoding and decoding
dbiInit.encoding = 'binary';
dbiInit.compression = existingCompression;

Check failure on line 191 in bin/copyDb.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v20)

Property 'compression' does not exist on type 'OpenDBIObject'.

Check failure on line 191 in bin/copyDb.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v24)

Property 'compression' does not exist on type 'OpenDBIObject'.

Check failure on line 191 in bin/copyDb.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v22)

Property 'compression' does not exist on type 'OpenDBIObject'.
//dbiInit.keyEncoding = 'binary';
const sourceDbi = rootStore.openDB(key, dbiInit);
sourceDbi.decoder = null;
sourceDbi.decoderCopies = false;
sourceDbi.encoding = 'binary';
dbiInit.compression = newCompression;

Check failure on line 197 in bin/copyDb.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v20)

Property 'compression' does not exist on type 'OpenDBIObject'.

Check failure on line 197 in bin/copyDb.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v24)

Property 'compression' does not exist on type 'OpenDBIObject'.

Check failure on line 197 in bin/copyDb.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v22)

Property 'compression' does not exist on type 'OpenDBIObject'.
const targetDbi = targetEnv.openDB(key, dbiInit);

Check failure on line 198 in bin/copyDb.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v20)

Argument of type 'OpenDBIObject' is not assignable to parameter of type 'DatabaseOptions'.

Check failure on line 198 in bin/copyDb.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v24)

Argument of type 'OpenDBIObject' is not assignable to parameter of type 'DatabaseOptions'.

Check failure on line 198 in bin/copyDb.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v22)

Argument of type 'OpenDBIObject' is not assignable to parameter of type 'DatabaseOptions'.
targetDbi.encoder = null;

Check failure on line 199 in bin/copyDb.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v20)

Property 'encoder' does not exist on type 'Database<any, Key>'.

Check failure on line 199 in bin/copyDb.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v24)

Property 'encoder' does not exist on type 'Database<any, Key>'.

Check failure on line 199 in bin/copyDb.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v22)

Property 'encoder' does not exist on type 'Database<any, Key>'.
console.log('copying', key, 'from', sourceDatabase, 'to', targetDatabasePath);
await copyDbi(sourceDbi, targetDbi, isPrimary, transaction);
}
Expand All @@ -209,6 +209,7 @@
async function copyDbi(sourceDbi, targetDbi, isPrimary, transaction) {
let recordsCopied = 0;
let bytesCopied = 0;
let skippedRecord = 0;
let retries = 10000000;
let start = null;
while (retries-- > 0) {
Expand All @@ -217,13 +218,26 @@
try {
start = key;
const { value, version } = sourceDbi.getEntry(key, { transaction });
// deleted entries should be 13 bytes long (8 for timestamp, 4 bytes for flags, 1 byte of the encoding of null)
if (value?.length < 14 && isPrimary) {
skippedRecord++;
continue;
}
written = targetDbi.put(key, value, isPrimary ? version : undefined);
recordsCopied++;
if (transaction.openTimer) transaction.openTimer = 0; // reset the timer, don't want it to time out
bytesCopied += (key?.length || 10) + value.length;
if (outstandingWrites++ > 5000) {
await written;
console.log('copied', recordsCopied, 'entries', bytesCopied, 'bytes');
console.log(
'copied',
recordsCopied,
'entries, skipped',
skippedRecord,
'delete records,',
bytesCopied,
'bytes'
);
outstandingWrites = 0;
}
} catch (error) {
Expand All @@ -238,7 +252,15 @@
);
}
}
console.log('finish copying, copied', recordsCopied, 'entries', bytesCopied, 'bytes');
console.log(
'finish copying, copied',
recordsCopied,
'entries, skipped',
skippedRecord,
'delete records,',
bytesCopied,
'bytes'
);
return;
} catch (error) {
// try to resume with a bigger key
Expand Down
12 changes: 6 additions & 6 deletions components/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
if (application.payload) {
// Given a payload, create a Readable from the Buffer or string
tarball = Readable.from(
application.payload instanceof Buffer ? application.payload : Buffer.from(application.payload, 'base64')

Check failure on line 83 in components/Application.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v20)

No overload matches this call.

Check failure on line 83 in components/Application.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v24)

No overload matches this call.

Check failure on line 83 in components/Application.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v22)

No overload matches this call.
);
} else {
// Given a package, there are a a couple options
Expand Down Expand Up @@ -119,12 +119,12 @@
}
} else {
// Given a package, resolve using `npm pack` (downloads the package as a tarball and writes the path to stdout)
const { stdout: tarballFilePath } = await nonInteractiveSpawn(
application.name,
'npm',
['pack', application.packageIdentifier],
parentDirPath
);
const {
stdout: tarballFilePath,
code,
stderr,
} = await nonInteractiveSpawn(application.name, 'npm', ['pack', application.packageIdentifier], parentDirPath);
if (code !== 0) throw new Error(`Failed to download package ${application.packageIdentifier}: ${stderr}`);
tarballPath = join(parentDirPath, tarballFilePath.trim());
// Create a Readable from the tarball
tarball = createReadStream(tarballPath);
Expand Down
7 changes: 6 additions & 1 deletion components/EntryHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
urlPath,
};
this.emit('all', entry);
this.emit(event, entry);

Check failure on line 114 in components/EntryHandler.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v20)

Argument of type '[AddFileEvent | ChangeFileEvent]' is not assignable to parameter of type '[entry: AddFileEvent] | [entry: ChangeFileEvent]'.

Check failure on line 114 in components/EntryHandler.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v24)

Argument of type '[AddFileEvent | ChangeFileEvent]' is not assignable to parameter of type '[entry: AddFileEvent] | [entry: ChangeFileEvent]'.

Check failure on line 114 in components/EntryHandler.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v22)

Argument of type '[AddFileEvent | ChangeFileEvent]' is not assignable to parameter of type '[entry: AddFileEvent] | [entry: ChangeFileEvent]'.
});
break;
}
Expand Down Expand Up @@ -139,7 +139,7 @@
urlPath,
};
this.emit('all', entry);
this.emit(event, entry);

Check failure on line 142 in components/EntryHandler.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v20)

Argument of type '[DirectoryEntryEvent]' is not assignable to parameter of type '[entry: AddDirectoryEvent] | [entry: UnlinkDirectoryEvent]'.

Check failure on line 142 in components/EntryHandler.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v24)

Argument of type '[DirectoryEntryEvent]' is not assignable to parameter of type '[entry: AddDirectoryEvent] | [entry: UnlinkDirectoryEvent]'.

Check failure on line 142 in components/EntryHandler.ts

View workflow job for this annotation

GitHub Actions / Integration Test (Node.js v22)

Argument of type '[DirectoryEntryEvent]' is not assignable to parameter of type '[entry: AddDirectoryEvent] | [entry: UnlinkDirectoryEvent]'.
break;
}
}
Expand All @@ -164,7 +164,12 @@
cwd: this.#component.directory,
persistent: false,
ignored: (path) => {
return path !== this.#component.directory && allowedBases.every((base) => !path.startsWith(base));
const normalizedPath = path.replace(/\\/g, '/');
const normalizedBases = allowedBases.map(base => base.replace(/\\/g, '/'));
return (
normalizedPath !== this.#component.directory.replace(/\\/g, '/') &&
normalizedBases.every((base) => !normalizedPath.startsWith(base))
);
},
})
.on('all', this.#handleAll.bind(this))
Expand Down
10 changes: 6 additions & 4 deletions components/Scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,13 @@ export class Scope extends EventEmitter {
};
}

#getFilesOption(): FilesOption | FileAndURLPathConfig | undefined {
#getFilesOption(): FileAndURLPathConfig | undefined {
const config = this.options.getAll();
if (config && typeof config === 'object' && config !== null && !Array.isArray(config) && 'files' in config) {
// TODO maybe some validation here?
return config.files as FilesOption | FileAndURLPathConfig;
if (config && typeof config === 'object' && config !== null && !Array.isArray(config) && 'files' in config /*&& validate config.files*/) {
return {
files: config.files as FilesOption,
urlPath: config.urlPath as string | undefined,
};
}
return undefined;
}
Expand Down
11 changes: 11 additions & 0 deletions config/configHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as path from 'node:path';
import * as hdbTerms from '../utility/hdbTerms.ts';

/**
* Get the backup directory path for the given Harper root
* @param hdbRoot - Harper root path
* @returns Full path to backup directory
*/
export function getBackupDirPath(hdbRoot: string): string {
return path.join(hdbRoot, hdbTerms.BACKUP_DIR_NAME);
}
81 changes: 79 additions & 2 deletions config/configUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const _ = require('lodash');
const { handleHDBError } = require('../utility/errors/hdbError.js');
const { HTTP_STATUS_CODES, HDB_ERROR_MSGS } = require('../utility/errors/commonErrors.js');
const { server } = require('../server/Server.ts');
const { getBackupDirPath } = require('./configHelpers.ts');

const { DATABASES_PARAM_CONFIG, CONFIG_PARAMS, CONFIG_PARAM_MAP } = hdbTerms;
const UNINIT_GET_CONFIG_ERR = 'Unable to get config value because config is uninitialized';
Expand Down Expand Up @@ -121,6 +122,11 @@ function createConfigFile(args, skipFsValidation = false) {

// Validates config doc and if required sets default values for some parameters.
validateConfig(configDoc, skipFsValidation);

// Apply HARPER_DEFAULT_CONFIG and HARPER_SET_CONFIG environment variables
// Must be called AFTER rootPath is set in configDoc
// Mutates configDoc in place
applyRuntimeEnvVarConfig(configDoc, null, { isInstall: true });
const configObj = configDoc.toJSON();
flatConfigObj = flattenConfig(configObj);

Expand Down Expand Up @@ -270,6 +276,9 @@ function initConfig(force = false) {

checkForUpdatedConfig(configDoc, configFilePath);

// Apply HARPER_DEFAULT_CONFIG and HARPER_SET_CONFIG environment variables
applyRuntimeEnvVarConfig(configDoc, configFilePath);

// Validates config doc and if required sets default values for some parameters.
validateConfig(configDoc);
const configObj = configDoc.toJSON();
Expand Down Expand Up @@ -545,8 +554,7 @@ function updateConfigValue(
function backupConfigFile(configPath, hdbRoot) {
try {
const backupFolderPath = path.join(
hdbRoot,
'backup',
getBackupDirPath(hdbRoot),
`${new Date(Date.now()).toISOString().replaceAll(':', '-')}-${hdbTerms.HDB_CONFIG_FILE}.bak`
);
fs.copySync(configPath, backupFolderPath);
Expand Down Expand Up @@ -706,6 +714,75 @@ function parseYamlDoc(filePath) {
return YAML.parseDocument(fs.readFileSync(filePath, 'utf8'), { simpleKeys: true });
}

/**
* Apply HARPER_DEFAULT_CONFIG and HARPER_SET_CONFIG environment variables at runtime
*
* This function performs the following:
* 1. Loads configuration state to track sources
* 2. Detects user edits (drift) to protect them from HARPER_DEFAULT_CONFIG
* 3. Applies HARPER_DEFAULT_CONFIG (respects user edits)
* 4. Applies HARPER_SET_CONFIG (overrides everything)
* 5. Handles deletions when keys removed from env vars
* 6. Saves updated state and persists changes to config file (if configFilePath provided)
*
* NOTE: This function performs multiple conversions (YAML → JSON → YAML) which is not
* efficient but provides clear separation of concerns. The conversions are necessary
* to handle YAML structure conflicts (e.g., when a boolean like 'threads: true' needs
* to become an object like 'threads: {count: 4}').
*
* @param {Document} configDoc - YAML document to modify (mutated in place)
* @param {string} [configFilePath] - Path to config file (optional, skips file write if not provided)
* @param {Object} [options] - Options to pass to applyRuntimeEnvConfig (e.g., {isInstall: true})
*/
function applyRuntimeEnvVarConfig(configDoc, configFilePath, options = {}) {
const defaultEnvValue = process.env.HARPER_DEFAULT_CONFIG;
const setEnvValue = process.env.HARPER_SET_CONFIG;

// No env vars set, skip entirely (zero overhead)
if (!defaultEnvValue && !setEnvValue) return;

const { applyRuntimeEnvConfig } = require('./harperConfigEnvVars.ts');

// Get rootPath for state file location
const rootPath = configDoc.getIn(['rootPath']);
if (!rootPath) {
logger.warn('Cannot apply runtime env config: rootPath not found in config');
return;
}

// Convert to JSON for processing
const configObj = configDoc.toJSON();

try {
// Apply env vars with source tracking and drift detection
applyRuntimeEnvConfig(configObj, rootPath, options);

// Convert back to YAML document and write to file
const mergedDoc = YAML.parseDocument(YAML.stringify(configObj), { simpleKeys: true });
Object.assign(configDoc, mergedDoc);
} catch (error) {
logger.error(`Failed to apply runtime env config: ${error.message}`);
throw error;
}

// We're done here if no config file to write to
if (!configFilePath) {
return;
}

// Persist changes to file
try {
if (configDoc.errors?.length > 0) {
throw new Error(`Error parsing harperdb-config.yaml: ${configDoc.errors}`);
}
fs.writeFileSync(configFilePath, String(configDoc));
logger.debug('Config file updated with runtime env var values');
} catch (error) {
logger.error(`Failed to write config file after applying runtime env vars: ${error.message}`);
throw error;
}
}

/**
* This function reads config settings from old settings file(before 4.0.0), aligns old keys to new keys, gets old
* values, and updates the in-memory object.
Expand Down
Loading
Loading