@harperfast/harper-pro 5.0.0-beta.5 → 5.0.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/bin/harper.js +1 -1
- package/core/bin/run.js +2 -2
- package/core/components/Application.ts +9 -5
- package/core/components/ApplicationScope.ts +3 -3
- package/core/components/componentLoader.ts +28 -15
- package/core/components/operations.js +13 -13
- package/core/components/operationsValidation.js +3 -3
- package/core/config/configUtils.js +20 -4
- package/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +3 -2
- package/core/resources/Resource.ts +17 -6
- package/core/resources/RocksTransactionLogStore.ts +8 -1
- package/core/resources/analytics/metadata.ts +1 -0
- package/core/resources/analytics/read.ts +1 -1
- package/core/resources/analytics/write.ts +43 -2
- package/core/resources/databases.ts +24 -15
- package/core/security/jsLoader.ts +182 -91
- package/core/server/REST.ts +20 -11
- package/core/server/http.ts +3 -3
- package/core/server/itc/serverHandlers.js +1 -1
- package/core/static/defaultConfig.yaml +1 -1
- package/core/utility/hdbTerms.ts +1 -0
- package/core/utility/logging/harper_logger.js +44 -11
- package/core/utility/logging/readLog.js +2 -2
- package/core/utility/npmUtilities.js +2 -2
- package/core/validation/configValidator.js +16 -8
- package/core/validation/readLogValidator.js +2 -2
- package/dist/core/bin/harper.js +1 -1
- package/dist/core/bin/run.js +2 -2
- package/dist/core/bin/run.js.map +1 -1
- package/dist/core/components/Application.js +9 -4
- package/dist/core/components/Application.js.map +1 -1
- package/dist/core/components/ApplicationScope.js +2 -2
- package/dist/core/components/ApplicationScope.js.map +1 -1
- package/dist/core/components/componentLoader.js +30 -16
- package/dist/core/components/componentLoader.js.map +1 -1
- package/dist/core/components/operations.js +13 -13
- package/dist/core/components/operations.js.map +1 -1
- package/dist/core/components/operationsValidation.js +3 -3
- package/dist/core/components/operationsValidation.js.map +1 -1
- package/dist/core/config/configUtils.js +23 -3
- package/dist/core/config/configUtils.js.map +1 -1
- package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +3 -2
- package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
- package/dist/core/resources/Resource.js +11 -4
- package/dist/core/resources/Resource.js.map +1 -1
- package/dist/core/resources/RocksTransactionLogStore.js +8 -1
- package/dist/core/resources/RocksTransactionLogStore.js.map +1 -1
- package/dist/core/resources/analytics/metadata.js +1 -0
- package/dist/core/resources/analytics/metadata.js.map +1 -1
- package/dist/core/resources/analytics/read.js +1 -1
- package/dist/core/resources/analytics/read.js.map +1 -1
- package/dist/core/resources/analytics/write.js +42 -0
- package/dist/core/resources/analytics/write.js.map +1 -1
- package/dist/core/resources/databases.js +19 -13
- package/dist/core/resources/databases.js.map +1 -1
- package/dist/core/security/jsLoader.js +167 -81
- package/dist/core/security/jsLoader.js.map +1 -1
- package/dist/core/server/REST.js +17 -10
- package/dist/core/server/REST.js.map +1 -1
- package/dist/core/server/http.js +2 -2
- package/dist/core/server/http.js.map +1 -1
- package/dist/core/server/itc/serverHandlers.js +1 -1
- package/dist/core/server/itc/serverHandlers.js.map +1 -1
- package/dist/core/utility/hdbTerms.js +1 -0
- package/dist/core/utility/hdbTerms.js.map +1 -1
- package/dist/core/utility/logging/harper_logger.js +47 -11
- package/dist/core/utility/logging/harper_logger.js.map +1 -1
- package/dist/core/utility/logging/readLog.js +2 -2
- package/dist/core/utility/logging/readLog.js.map +1 -1
- package/dist/core/utility/npmUtilities.js +2 -2
- package/dist/core/utility/npmUtilities.js.map +1 -1
- package/dist/core/validation/configValidator.js +18 -8
- package/dist/core/validation/configValidator.js.map +1 -1
- package/dist/core/validation/readLogValidator.js +2 -2
- package/dist/core/validation/readLogValidator.js.map +1 -1
- package/dist/replication/nodeIdMapping.js +1 -1
- package/dist/replication/nodeIdMapping.js.map +1 -1
- package/dist/replication/replicationConnection.js +1 -4
- package/dist/replication/replicationConnection.js.map +1 -1
- package/npm-shrinkwrap.json +506 -501
- package/package.json +7 -4
- package/replication/nodeIdMapping.ts +1 -1
- package/replication/replicationConnection.ts +9 -4
- package/static/defaultConfig.yaml +3 -0
- package/studio/web/assets/{index-ZhLX9iRh.js → index-ClD_q6ya.js} +5 -5
- package/studio/web/assets/{index-ZhLX9iRh.js.map → index-ClD_q6ya.js.map} +1 -1
- package/studio/web/assets/{index.lazy-DzgnppiN.js → index.lazy-CXzU1gVu.js} +2 -2
- package/studio/web/assets/{index.lazy-DzgnppiN.js.map → index.lazy-CXzU1gVu.js.map} +1 -1
- package/studio/web/assets/{profile-DJ9V18dX.js → profile-DCNVg5yY.js} +2 -2
- package/studio/web/assets/{profile-DJ9V18dX.js.map → profile-DCNVg5yY.js.map} +1 -1
- package/studio/web/assets/{status-DKZUoEUd.js → status-CoGlcjSB.js} +2 -2
- package/studio/web/assets/{status-DKZUoEUd.js.map → status-CoGlcjSB.js.map} +1 -1
- package/studio/web/index.html +1 -1
|
@@ -20,6 +20,14 @@ import { EventEmitter } from 'node:events';
|
|
|
20
20
|
|
|
21
21
|
type Lockdown = 'none' | 'freeze' | 'ses';
|
|
22
22
|
const APPLICATIONS_LOCKDOWN: Lockdown = env.get(CONFIG_PARAMS.APPLICATIONS_LOCKDOWN);
|
|
23
|
+
const HARPER_MODULE_IDS = new Set([
|
|
24
|
+
'harper',
|
|
25
|
+
'harperdb',
|
|
26
|
+
'harperdb/v1',
|
|
27
|
+
'harperdb/v2',
|
|
28
|
+
'@harperfast/harper',
|
|
29
|
+
'@harperfast/harper-pro',
|
|
30
|
+
]);
|
|
23
31
|
|
|
24
32
|
let lockedDown = false;
|
|
25
33
|
/**
|
|
@@ -138,8 +146,9 @@ function parseJsonModule(source: string, url: string): any {
|
|
|
138
146
|
* Load a module using Node's vm.Module API with (not really secure) sandboxing
|
|
139
147
|
*/
|
|
140
148
|
async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
|
|
141
|
-
const moduleCache = new Map<string, SourceTextModule | SyntheticModule
|
|
149
|
+
const moduleCache = new Map<string, Promise<SourceTextModule | SyntheticModule>>();
|
|
142
150
|
const linkingPromises = new Map<string, Promise<void>>();
|
|
151
|
+
const cjsCache = new Map<string, { exports: any }>();
|
|
143
152
|
|
|
144
153
|
// Create a secure context with limited globals
|
|
145
154
|
const contextObject = getGlobalObject(scope);
|
|
@@ -149,30 +158,38 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
|
|
|
149
158
|
* Resolve module specifier to absolute URL
|
|
150
159
|
*/
|
|
151
160
|
function resolveModule(specifier: string, referrer: string): string {
|
|
152
|
-
if (specifier
|
|
153
|
-
|
|
161
|
+
if (HARPER_MODULE_IDS.has(specifier)) {
|
|
162
|
+
return 'harper'; // resolve any harper package as an alias to a single synthetic module
|
|
163
|
+
}
|
|
164
|
+
const parts = specifier.split('/');
|
|
165
|
+
if (parts[0] === 'harper') {
|
|
166
|
+
// block harper/* for now (reserving for potential future use)
|
|
154
167
|
throw new Error(`Module ${specifier} is not allowed, may only access the 'harper' module`);
|
|
155
168
|
}
|
|
156
|
-
if (
|
|
169
|
+
if (parts[0] === 'file:') {
|
|
157
170
|
return specifier;
|
|
158
171
|
}
|
|
159
|
-
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
if (isAbsolute(resolved)) {
|
|
163
|
-
return pathToFileURL(resolved).toString();
|
|
164
|
-
}
|
|
165
|
-
return resolved;
|
|
172
|
+
const resolved = createRequire(referrer).resolve(specifier);
|
|
173
|
+
if (isAbsolute(resolved)) {
|
|
174
|
+
return pathToFileURL(resolved).toString();
|
|
166
175
|
}
|
|
167
|
-
|
|
168
|
-
return specifier;
|
|
176
|
+
return resolved;
|
|
169
177
|
}
|
|
170
178
|
|
|
171
179
|
/**
|
|
172
180
|
* Load a CommonJS module in our private context
|
|
173
181
|
*/
|
|
174
182
|
function loadCJS(url: string, source: string): { exports: any } {
|
|
183
|
+
// Check cache first to handle circular dependencies
|
|
184
|
+
if (cjsCache.has(url)) {
|
|
185
|
+
return cjsCache.get(url)!;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Create module object and cache it immediately (before execution)
|
|
189
|
+
// This allows circular dependencies to get a reference to the incomplete module
|
|
175
190
|
const cjsModule = { exports: {} };
|
|
191
|
+
cjsCache.set(url, cjsModule);
|
|
192
|
+
|
|
176
193
|
if (url.endsWith('.json')) {
|
|
177
194
|
cjsModule.exports = parseJsonModule(source, url);
|
|
178
195
|
return cjsModule;
|
|
@@ -200,7 +217,7 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
|
|
|
200
217
|
filename: url,
|
|
201
218
|
async importModuleDynamically(specifier: string, script) {
|
|
202
219
|
const resolvedUrl = resolveModule(specifier, script.sourceURL);
|
|
203
|
-
const useContainment = specifier.startsWith('.') || scope.dependencyContainment;
|
|
220
|
+
const useContainment = specifier.startsWith('.') || scope.dependencyContainment !== false;
|
|
204
221
|
const dynamicModule = await loadModuleWithCache(resolvedUrl, useContainment);
|
|
205
222
|
return dynamicModule;
|
|
206
223
|
},
|
|
@@ -217,31 +234,88 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
|
|
|
217
234
|
}
|
|
218
235
|
function loadCJSModule(url: string, source: string, usePrivateGlobal: boolean): SyntheticModule {
|
|
219
236
|
const cjsModule = usePrivateGlobal ? loadCJS(url, source) : { exports: require(url) };
|
|
220
|
-
|
|
237
|
+
let exports = cjsModule.exports;
|
|
238
|
+
if (exports.default === undefined) {
|
|
239
|
+
// provide the default export for compatibility
|
|
240
|
+
exports = { default: exports, ...exports };
|
|
241
|
+
}
|
|
242
|
+
const exportNames = Object.keys(exports);
|
|
243
|
+
|
|
221
244
|
const synModule = new SyntheticModule(
|
|
222
|
-
exportNames
|
|
245
|
+
exportNames,
|
|
223
246
|
function () {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
this.setExport(key, cjsModule.exports[key]);
|
|
227
|
-
}
|
|
228
|
-
} else {
|
|
229
|
-
this.setExport('default', cjsModule.exports);
|
|
247
|
+
for (const key of exportNames) {
|
|
248
|
+
this.setExport(key, exports[key]);
|
|
230
249
|
}
|
|
231
250
|
},
|
|
232
251
|
{ identifier: url, context }
|
|
233
252
|
);
|
|
234
|
-
|
|
253
|
+
// Don't cache here - let getOrCreateModule handle caching
|
|
235
254
|
return synModule;
|
|
236
255
|
}
|
|
237
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Check if a package (or any of its dependencies) depends on harper
|
|
259
|
+
* Expects a file URL like: file:///path/to/node_modules/package-name/dist/index.js
|
|
260
|
+
*/
|
|
261
|
+
function packageDependsOnHarper(fileUrl: string): boolean {
|
|
262
|
+
try {
|
|
263
|
+
// Convert file:// URL to path
|
|
264
|
+
const filePath = fileURLToPath(fileUrl);
|
|
265
|
+
|
|
266
|
+
// Find the node_modules directory and package name
|
|
267
|
+
// Example: /path/to/node_modules/package-name/dist/index.js
|
|
268
|
+
// or: /path/to/node_modules/@scope/package-name/dist/index.js
|
|
269
|
+
const nodeModulesMarker = '/node_modules/';
|
|
270
|
+
const nodeModulesIndex = filePath.lastIndexOf(nodeModulesMarker);
|
|
271
|
+
if (nodeModulesIndex === -1) return false;
|
|
272
|
+
|
|
273
|
+
// Get the part after /node_modules/
|
|
274
|
+
const afterNodeModules = filePath.substring(nodeModulesIndex + nodeModulesMarker.length);
|
|
275
|
+
const parts = afterNodeModules.split('/');
|
|
276
|
+
|
|
277
|
+
// Handle scoped packages (@scope/package-name) vs regular packages (package-name)
|
|
278
|
+
const beforeNodeModules = filePath.substring(0, nodeModulesIndex);
|
|
279
|
+
const packageRoot = parts[0].startsWith('@')
|
|
280
|
+
? join(beforeNodeModules, 'node_modules', parts[0], parts[1])
|
|
281
|
+
: join(beforeNodeModules, 'node_modules', parts[0]);
|
|
282
|
+
|
|
283
|
+
// Read package.json from the package root
|
|
284
|
+
const packageJsonPath = join(packageRoot, 'package.json');
|
|
285
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
286
|
+
|
|
287
|
+
const deps = {
|
|
288
|
+
...packageJson.dependencies,
|
|
289
|
+
...packageJson.devDependencies,
|
|
290
|
+
...packageJson.peerDependencies,
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// Check if harper is a direct dependency
|
|
294
|
+
return Object.keys(deps).some((dep) => HARPER_MODULE_IDS.has(dep));
|
|
295
|
+
} catch {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
238
300
|
/**
|
|
239
301
|
* Linker function for module resolution during instantiation
|
|
240
302
|
*/
|
|
241
303
|
async function linker(specifier: string, referencingModule: SourceTextModule | SyntheticModule) {
|
|
242
304
|
const resolvedUrl = resolveModule(specifier, referencingModule.identifier);
|
|
243
305
|
|
|
244
|
-
|
|
306
|
+
// Determine if we should use VM containment for this module
|
|
307
|
+
let useContainment = specifier.startsWith('.'); // Always contain relative imports
|
|
308
|
+
|
|
309
|
+
if (!useContainment && scope.dependencyContainment !== false) {
|
|
310
|
+
// For npm packages, check if they depend on harper
|
|
311
|
+
if (resolvedUrl.startsWith('file://') && resolvedUrl.includes('node_modules')) {
|
|
312
|
+
useContainment = packageDependsOnHarper(resolvedUrl);
|
|
313
|
+
} else {
|
|
314
|
+
// Non-file URLs (bare specifiers) - use default behavior
|
|
315
|
+
useContainment = scope.dependencyContainment === true;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
245
319
|
// Return the module immediately (even if not yet linked) to support circular dependencies
|
|
246
320
|
return await getOrCreateModule(resolvedUrl, useContainment);
|
|
247
321
|
}
|
|
@@ -256,7 +330,7 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
|
|
|
256
330
|
}
|
|
257
331
|
|
|
258
332
|
// Create the module and cache it immediately (before linking)
|
|
259
|
-
const module =
|
|
333
|
+
const module = createModule(url, usePrivateGlobal);
|
|
260
334
|
moduleCache.set(url, module);
|
|
261
335
|
|
|
262
336
|
return module;
|
|
@@ -270,10 +344,11 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
|
|
|
270
344
|
|
|
271
345
|
// Only link/evaluate once per module
|
|
272
346
|
if (!linkingPromises.has(url)) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
);
|
|
347
|
+
const linkingPromise = (async () => {
|
|
348
|
+
await module.link(linker);
|
|
349
|
+
await module.evaluate();
|
|
350
|
+
})();
|
|
351
|
+
linkingPromises.set(url, linkingPromise);
|
|
277
352
|
}
|
|
278
353
|
|
|
279
354
|
// Wait for linking to complete
|
|
@@ -288,7 +363,7 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
|
|
|
288
363
|
let module: SourceTextModule | SyntheticModule;
|
|
289
364
|
|
|
290
365
|
// Handle special built-in modules
|
|
291
|
-
if (url === 'harper'
|
|
366
|
+
if (url === 'harper') {
|
|
292
367
|
let harperExports = getHarperExports(scope);
|
|
293
368
|
module = new SyntheticModule(
|
|
294
369
|
Object.keys(harperExports),
|
|
@@ -299,7 +374,7 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
|
|
|
299
374
|
},
|
|
300
375
|
{ identifier: url, context }
|
|
301
376
|
);
|
|
302
|
-
} else if (url.startsWith('file://')) {
|
|
377
|
+
} else if (url.startsWith('file://') && usePrivateGlobal) {
|
|
303
378
|
checkAllowedModulePath(url, scope.verifyPath);
|
|
304
379
|
let source = await readFile(new URL(url), { encoding: 'utf-8' });
|
|
305
380
|
|
|
@@ -320,67 +395,51 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
|
|
|
320
395
|
source = await stripTypeScriptTypes(source);
|
|
321
396
|
}
|
|
322
397
|
|
|
323
|
-
// Try
|
|
398
|
+
// Try CJS first since it will fail fast with clear syntax errors on ESM syntax
|
|
324
399
|
try {
|
|
325
|
-
module =
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
module = loadCJSModule(url, source, usePrivateGlobal);
|
|
345
|
-
} else {
|
|
346
|
-
throw err;
|
|
400
|
+
module = loadCJSModule(url, source, usePrivateGlobal);
|
|
401
|
+
} catch {
|
|
402
|
+
// If CJS loading fails (likely due to ESM syntax like import/export), try ESM
|
|
403
|
+
try {
|
|
404
|
+
module = new SourceTextModule(source, {
|
|
405
|
+
identifier: url,
|
|
406
|
+
context,
|
|
407
|
+
initializeImportMeta(meta) {
|
|
408
|
+
meta.url = url;
|
|
409
|
+
},
|
|
410
|
+
async importModuleDynamically(specifier: string) {
|
|
411
|
+
const resolvedUrl = resolveModule(specifier, url);
|
|
412
|
+
const dynamicModule = await loadModuleWithCache(resolvedUrl, true);
|
|
413
|
+
return dynamicModule;
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
} catch (esmErr) {
|
|
417
|
+
// Both failed - throw the ESM error as it's likely more relevant
|
|
418
|
+
throw esmErr;
|
|
347
419
|
}
|
|
348
420
|
}
|
|
349
421
|
}
|
|
350
422
|
} else {
|
|
351
423
|
const replacedModule = checkAllowedModulePath(url, scope.verifyPath);
|
|
352
|
-
// For Node.js built-in modules (node:) and npm packages
|
|
424
|
+
// For Node.js built-in modules (node:) and npm packages without dependency containment
|
|
353
425
|
// Always try require first to properly handle CJS modules with named exports
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
function () {
|
|
361
|
-
if (exportNames.length > 0) {
|
|
362
|
-
for (const key of exportNames) {
|
|
363
|
-
this.setExport(key, cjsExports[key]);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
this.setExport('default', cjsExports);
|
|
367
|
-
},
|
|
368
|
-
{ identifier: url, context }
|
|
369
|
-
);
|
|
370
|
-
} catch {
|
|
371
|
-
// Fall back to dynamic import for ESM packages
|
|
372
|
-
const importedModule = await import(url);
|
|
373
|
-
const exportNames = Object.keys(importedModule);
|
|
374
|
-
module = new SyntheticModule(
|
|
375
|
-
exportNames,
|
|
376
|
-
function () {
|
|
377
|
-
for (const key of exportNames) {
|
|
378
|
-
this.setExport(key, importedModule[key]);
|
|
379
|
-
}
|
|
380
|
-
},
|
|
381
|
-
{ identifier: url, context }
|
|
382
|
-
);
|
|
426
|
+
// Fall back to dynamic import for ESM packages
|
|
427
|
+
let importedModule = replacedModule ?? (await import(url));
|
|
428
|
+
const cjsModule = importedModule['module.exports'];
|
|
429
|
+
if (cjsModule) {
|
|
430
|
+
// back-compat import
|
|
431
|
+
importedModule = importedModule.default ? { default: importedModule.default, ...cjsModule } : cjsModule;
|
|
383
432
|
}
|
|
433
|
+
const exportNames = Object.keys(importedModule);
|
|
434
|
+
module = new SyntheticModule(
|
|
435
|
+
exportNames,
|
|
436
|
+
function () {
|
|
437
|
+
for (const key of exportNames) {
|
|
438
|
+
this.setExport(key, importedModule[key]);
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
{ identifier: url, context }
|
|
442
|
+
);
|
|
384
443
|
}
|
|
385
444
|
|
|
386
445
|
return module;
|
|
@@ -404,7 +463,15 @@ async function getCompartment(scope: ApplicationScope, globals) {
|
|
|
404
463
|
{
|
|
405
464
|
name: 'harper-app',
|
|
406
465
|
resolveHook(moduleSpecifier, moduleReferrer) {
|
|
407
|
-
if (moduleSpecifier
|
|
466
|
+
if (HARPER_MODULE_IDS.has(moduleSpecifier)) {
|
|
467
|
+
return 'harper'; // resolve any harper package as an alias to a single synthetic module
|
|
468
|
+
}
|
|
469
|
+
const parts = moduleSpecifier.split('/');
|
|
470
|
+
if (parts[0] === 'harper') {
|
|
471
|
+
// block harper/* for now (reserving for potential future use)
|
|
472
|
+
throw new Error(`Module ${moduleSpecifier} is not allowed, may only access the 'harper' module`);
|
|
473
|
+
}
|
|
474
|
+
|
|
408
475
|
const resolved = createRequire(moduleReferrer).resolve(moduleSpecifier);
|
|
409
476
|
if (isAbsolute(resolved)) {
|
|
410
477
|
const resolvedURL = pathToFileURL(resolved).toString();
|
|
@@ -523,6 +590,25 @@ function getHarperExports(scope: ApplicationScope) {
|
|
|
523
590
|
authenticateUser: server.authenticateUser,
|
|
524
591
|
operation: server.operation,
|
|
525
592
|
contentTypes,
|
|
593
|
+
Attribute: undefined,
|
|
594
|
+
Config: undefined,
|
|
595
|
+
ConfigValue: undefined,
|
|
596
|
+
Context: undefined,
|
|
597
|
+
FileAndURLPathConfig: undefined,
|
|
598
|
+
FilesOption: undefined,
|
|
599
|
+
FilesOptionObject: undefined,
|
|
600
|
+
IterableEventQueue: undefined,
|
|
601
|
+
Logger: undefined,
|
|
602
|
+
Query: undefined,
|
|
603
|
+
RecordObject: undefined,
|
|
604
|
+
RequestTargetOrId: undefined,
|
|
605
|
+
ResourceInterface: undefined,
|
|
606
|
+
Scope: undefined,
|
|
607
|
+
Session: undefined,
|
|
608
|
+
SourceContext: undefined,
|
|
609
|
+
SubscriptionRequest: undefined,
|
|
610
|
+
Table: undefined,
|
|
611
|
+
User: undefined,
|
|
526
612
|
};
|
|
527
613
|
}
|
|
528
614
|
const ALLOWED_NODE_BUILTIN_MODULES = env.get(CONFIG_PARAMS.APPLICATIONS_ALLOWEDBUILTINMODULES)
|
|
@@ -534,14 +620,19 @@ const ALLOWED_NODE_BUILTIN_MODULES = env.get(CONFIG_PARAMS.APPLICATIONS_ALLOWEDB
|
|
|
534
620
|
},
|
|
535
621
|
};
|
|
536
622
|
const ALLOWED_COMMANDS = new Set(env.get(CONFIG_PARAMS.APPLICATIONS_ALLOWEDSPAWNCOMMANDS) ?? []);
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
623
|
+
const child_processConstrained = {
|
|
624
|
+
exec: createSpawn(child_process.exec),
|
|
625
|
+
execFile: createSpawn(child_process.execFile),
|
|
626
|
+
fork: createSpawn(child_process.fork, true), // this is launching node, so deemed safe
|
|
627
|
+
spawn: createSpawn(child_process.spawn),
|
|
628
|
+
execSync: function () {
|
|
629
|
+
throw new Error('execSync is not allowed');
|
|
543
630
|
},
|
|
544
631
|
};
|
|
632
|
+
child_processConstrained.default = child_processConstrained;
|
|
633
|
+
const REPLACED_BUILTIN_MODULES = {
|
|
634
|
+
child_process: child_processConstrained,
|
|
635
|
+
};
|
|
545
636
|
/**
|
|
546
637
|
* Creates a ChildProcess-like object for an existing process
|
|
547
638
|
*/
|
package/core/server/REST.ts
CHANGED
|
@@ -4,7 +4,7 @@ import * as harperLogger from '../utility/logging/harper_logger.js';
|
|
|
4
4
|
import { ServerOptions } from 'http';
|
|
5
5
|
import { ServerError, ClientError } from '../utility/errors/hdbError.js';
|
|
6
6
|
import { Resources } from '../resources/Resources.ts';
|
|
7
|
-
import { Resource } from '../resources/Resource.ts';
|
|
7
|
+
import { Resource, missingMethod, allowedMethods } from '../resources/Resource.ts';
|
|
8
8
|
import { IterableEventQueue } from '../resources/IterableEventQueue.ts';
|
|
9
9
|
import { transaction } from '../resources/transaction.ts';
|
|
10
10
|
import { Headers, mergeHeaders } from '../server/serverHelpers/Headers.ts';
|
|
@@ -114,29 +114,38 @@ async function http(request: Context & Request, nextHandler) {
|
|
|
114
114
|
switch (method) {
|
|
115
115
|
case 'GET':
|
|
116
116
|
case 'HEAD':
|
|
117
|
-
return resource.get(target, request);
|
|
117
|
+
return resource.get ? resource.get(target, request) : missingMethod(resource, 'get');
|
|
118
118
|
case 'POST':
|
|
119
|
-
return resource.post(target, request.data, request);
|
|
119
|
+
return resource.post ? resource.post(target, request.data, request) : missingMethod(resource, 'post');
|
|
120
120
|
case 'PUT':
|
|
121
|
-
return resource.put(target, request.data, request);
|
|
121
|
+
return resource.put ? resource.put(target, request.data, request) : missingMethod(resource, 'put');
|
|
122
122
|
case 'DELETE':
|
|
123
|
-
return resource.delete(target, request);
|
|
123
|
+
return resource.delete ? resource.delete(target, request) : missingMethod(resource, 'delete');
|
|
124
124
|
case 'PATCH':
|
|
125
|
-
return resource.patch(target, request.data, request);
|
|
125
|
+
return resource.patch ? resource.patch(target, request.data, request) : missingMethod(resource, 'patch');
|
|
126
126
|
case 'OPTIONS': // used primarily for CORS
|
|
127
|
-
headers.setIfNone(
|
|
127
|
+
headers.setIfNone(
|
|
128
|
+
'Allow',
|
|
129
|
+
allowedMethods(resource)
|
|
130
|
+
.map((method) => method.toUpperCase())
|
|
131
|
+
.join(', ')
|
|
132
|
+
);
|
|
128
133
|
return;
|
|
129
134
|
case 'CONNECT':
|
|
130
135
|
// websockets? and event-stream
|
|
131
|
-
return resource.connect(target, null, request);
|
|
136
|
+
return resource.connect ? resource.connect(target, null, request) : missingMethod(resource, 'connect');
|
|
132
137
|
case 'TRACE':
|
|
133
138
|
return 'Harper is the terminating server';
|
|
134
139
|
case 'QUERY':
|
|
135
|
-
return resource.query(target, request.data, request);
|
|
140
|
+
return resource.query ? resource.query(target, request.data, request) : missingMethod(resource, 'query');
|
|
136
141
|
case 'COPY': // methods suggested from webdav RFC 4918
|
|
137
|
-
return resource.copy
|
|
142
|
+
return resource.copy
|
|
143
|
+
? resource.copy(target, headersObject.destination, request)
|
|
144
|
+
: missingMethod(resource, 'copy');
|
|
138
145
|
case 'MOVE':
|
|
139
|
-
return resource.move
|
|
146
|
+
return resource.move
|
|
147
|
+
? resource.move(target, headersObject.destination, request)
|
|
148
|
+
: missingMethod(resource, 'move');
|
|
140
149
|
case 'BREW': // RFC 2324
|
|
141
150
|
throw new ClientError("Harper is short and stout and can't brew coffee", 418);
|
|
142
151
|
default:
|
package/core/server/http.ts
CHANGED
|
@@ -8,7 +8,7 @@ import harperLogger from '../utility/logging/harper_logger.js';
|
|
|
8
8
|
import { parentPort } from 'node:worker_threads';
|
|
9
9
|
import env from '../utility/environment/environmentManager.js';
|
|
10
10
|
import * as terms from '../utility/hdbTerms.ts';
|
|
11
|
-
import {
|
|
11
|
+
import { getConfigPath } from '../config/configUtils.js';
|
|
12
12
|
import { getTicketKeys } from './threads/manageThreads.js';
|
|
13
13
|
import { createTLSSelector } from '../security/keys.js';
|
|
14
14
|
import { createSecureServer } from 'node:http2';
|
|
@@ -181,7 +181,7 @@ function getPorts(options) {
|
|
|
181
181
|
|
|
182
182
|
if (options?.usageType === 'operations-api' && env.get(terms.CONFIG_PARAMS.OPERATIONSAPI_NETWORK_DOMAINSOCKET)) {
|
|
183
183
|
ports.push({
|
|
184
|
-
port:
|
|
184
|
+
port: getConfigPath(terms.CONFIG_PARAMS.OPERATIONSAPI_NETWORK_DOMAINSOCKET),
|
|
185
185
|
secure: false,
|
|
186
186
|
});
|
|
187
187
|
}
|
|
@@ -209,7 +209,7 @@ function getHTTPServer(port: number, secure: boolean, options: ServerOptions) {
|
|
|
209
209
|
setPortServerMap(port, { protocol_name: secure ? 'HTTPS' : 'HTTP', name: getComponentName() });
|
|
210
210
|
if (!httpServers[port]) {
|
|
211
211
|
// TODO: These should all come from httpOptions or operationsApiOptions
|
|
212
|
-
const serverPrefix = isOperationsServer ? 'operationsApi_network' : 'http';
|
|
212
|
+
const serverPrefix = isOperationsServer ? 'operationsApi_network' : (usageType ?? 'http');
|
|
213
213
|
const keepAliveTimeout = env.get(serverPrefix + '_keepAliveTimeout');
|
|
214
214
|
const requestTimeout = env.get(serverPrefix + '_timeout');
|
|
215
215
|
const headersTimeout = env.get(serverPrefix + '_headersTimeout');
|
|
@@ -149,7 +149,7 @@ async function componentStatusRequestHandler(event) {
|
|
|
149
149
|
if (originatorThreadId === undefined) {
|
|
150
150
|
hdbLogger.debug('No originator threadId, falling back to broadcast');
|
|
151
151
|
} else {
|
|
152
|
-
hdbLogger.
|
|
152
|
+
hdbLogger.debug(`Failed to send direct response to thread ${originatorThreadId}, falling back to broadcast`);
|
|
153
153
|
}
|
|
154
154
|
await sendItcEvent(responseMessage);
|
|
155
155
|
}
|
package/core/utility/hdbTerms.ts
CHANGED
|
@@ -426,6 +426,7 @@ export const LEGACY_CONFIG_PARAMS = {
|
|
|
426
426
|
export const CONFIG_PARAMS = {
|
|
427
427
|
ANALYTICS_AGGREGATEPERIOD: 'analytics_aggregatePeriod',
|
|
428
428
|
ANALYTICS_REPLICATE: 'analytics_replicate',
|
|
429
|
+
ANALYTICS_STORAGEINTERVAL: 'analytics_storageInterval',
|
|
429
430
|
AUTHENTICATION_AUTHORIZELOCAL: 'authentication_authorizeLocal',
|
|
430
431
|
AUTHENTICATION_CACHETTL: 'authentication_cacheTTL',
|
|
431
432
|
AUTHENTICATION_COOKIE_DOMAINS: 'authentication_cookie_domains',
|
|
@@ -82,7 +82,7 @@ function updateLogger(logger, logOptions, name) {
|
|
|
82
82
|
logger.logToStdstreams = logOptions.stdStreams ?? false;
|
|
83
83
|
// if there is a configured tag or if a component is logging to default/main log path, use the component name as the tag
|
|
84
84
|
// to differentiate it
|
|
85
|
-
logger.tag = logOptions.tag ?? (mainLogger.path === logger.path && name);
|
|
85
|
+
logger.tag = logOptions.tag ?? ((mainLogger.path === logger.path || externalLogger.path === logger.path) && name);
|
|
86
86
|
}
|
|
87
87
|
// creates a logger where the methods are only defined if they are within the log level.
|
|
88
88
|
// Using this conditional logger means that every method call must be optional like log.trace?.('message),
|
|
@@ -98,6 +98,14 @@ function updateConditional(logger) {
|
|
|
98
98
|
conditional.debug = LOG_LEVEL_HIERARCHY.debug >= logger.level ? logger.debug.bind(logger) : undefined;
|
|
99
99
|
conditional.trace = LOG_LEVEL_HIERARCHY.trace >= logger.level ? logger.trace.bind(logger) : undefined;
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Resolve a config path value against rootPath if it is relative.
|
|
103
|
+
*/
|
|
104
|
+
function resolveLogPath(configPath, rootPath) {
|
|
105
|
+
if (!configPath || !rootPath) return configPath;
|
|
106
|
+
if (pathModule.isAbsolute(configPath)) return configPath;
|
|
107
|
+
return pathModule.resolve(rootPath, configPath);
|
|
108
|
+
}
|
|
101
109
|
async function updateLogSettings() {
|
|
102
110
|
if (!rootConfig) {
|
|
103
111
|
// set up the initial watcher
|
|
@@ -109,11 +117,23 @@ async function updateLogSettings() {
|
|
|
109
117
|
}
|
|
110
118
|
let rootConfigObject = rootConfig.config;
|
|
111
119
|
const logOptions = rootConfigObject.logging ?? {};
|
|
120
|
+
// Resolve relative paths against rootPath from the same config
|
|
121
|
+
const rootPath = rootConfigObject.rootPath;
|
|
122
|
+
if (logOptions.root) {
|
|
123
|
+
logOptions.root = resolveLogPath(logOptions.root, rootPath);
|
|
124
|
+
}
|
|
125
|
+
if (logOptions.rotation?.path) {
|
|
126
|
+
logOptions.rotation.path = resolveLogPath(logOptions.rotation.path, rootPath);
|
|
127
|
+
}
|
|
112
128
|
updateLogger(mainLogger, logOptions);
|
|
113
129
|
logFilePath = mainLogger.path;
|
|
114
130
|
logConsole = logOptions.console ?? false;
|
|
115
131
|
if (logOptions.external) {
|
|
116
132
|
updateLogger(externalLogger, logOptions.external);
|
|
133
|
+
for (let [name, component] of mainLogger.components) {
|
|
134
|
+
if (!(rootConfigObject[name] && rootConfigObject[name].logging) && component.isExternal)
|
|
135
|
+
updateLogger(component, logOptions.external, name);
|
|
136
|
+
}
|
|
117
137
|
}
|
|
118
138
|
for (const name in rootConfigObject) {
|
|
119
139
|
// we now scan each component to see if it has logging individual configured
|
|
@@ -121,7 +141,8 @@ async function updateLogSettings() {
|
|
|
121
141
|
if (component.logging) {
|
|
122
142
|
updateLogger(mainLogger.forComponent(name), component.logging, name);
|
|
123
143
|
} else if (mainLogger.hasComponent(name)) {
|
|
124
|
-
|
|
144
|
+
const componentLogger = mainLogger.forComponent(name);
|
|
145
|
+
updateLogger(componentLogger, (componentLogger.isExternal && logOptions.external) ?? logOptions, name);
|
|
125
146
|
}
|
|
126
147
|
}
|
|
127
148
|
}
|
|
@@ -226,7 +247,7 @@ module.exports = {
|
|
|
226
247
|
createLogger,
|
|
227
248
|
logsAtLevel,
|
|
228
249
|
getLogFilePath: () => logFilePath,
|
|
229
|
-
forComponent: (name) => mainLogger.forComponent(name),
|
|
250
|
+
forComponent: (name, isExternal) => mainLogger.forComponent(name, isExternal),
|
|
230
251
|
setMainLogger,
|
|
231
252
|
setLogLevel,
|
|
232
253
|
OUTPUTS,
|
|
@@ -273,6 +294,9 @@ module.exports.externalLogger = {
|
|
|
273
294
|
loggerWithTag(tag) {
|
|
274
295
|
return externalLogger.withTag(tag);
|
|
275
296
|
},
|
|
297
|
+
forComponent(name) {
|
|
298
|
+
return externalLogger.forComponent(name);
|
|
299
|
+
},
|
|
276
300
|
};
|
|
277
301
|
_assignPackageExport('logger', module.exports.externalLogger);
|
|
278
302
|
|
|
@@ -467,7 +491,7 @@ function createLogger({
|
|
|
467
491
|
component,
|
|
468
492
|
}) {
|
|
469
493
|
if (!logLevel) logLevel = 'info';
|
|
470
|
-
let level = LOG_LEVEL_HIERARCHY[logLevel];
|
|
494
|
+
let level = typeof logLevel === 'number' ? logLevel : LOG_LEVEL_HIERARCHY[logLevel];
|
|
471
495
|
let logger;
|
|
472
496
|
/**
|
|
473
497
|
* Log to std out and/or file
|
|
@@ -549,25 +573,29 @@ function createLogger({
|
|
|
549
573
|
logger.logToStdstreams = logToStdstreams;
|
|
550
574
|
if (!component) {
|
|
551
575
|
let components = new Map();
|
|
552
|
-
logger.forComponent = function (name) {
|
|
576
|
+
logger.forComponent = function (name, isExternal = false) {
|
|
553
577
|
let componentLogger = components.get(name);
|
|
554
578
|
if (!componentLogger) {
|
|
579
|
+
const protoLogger = isExternal ? externalLogger : logger;
|
|
555
580
|
componentLogger = createLogger({
|
|
556
|
-
path:
|
|
557
|
-
level:
|
|
558
|
-
stdStreams: logToStdstreams,
|
|
559
|
-
isExternalInstance: name === 'external',
|
|
560
|
-
rotation,
|
|
581
|
+
path: protoLogger.path,
|
|
582
|
+
level: protoLogger.level,
|
|
583
|
+
stdStreams: protoLogger.logToStdstreams,
|
|
584
|
+
isExternalInstance: isExternal || name === 'external',
|
|
585
|
+
rotation: protoLogger.rotation,
|
|
561
586
|
writeToLog,
|
|
562
587
|
component: true,
|
|
563
588
|
});
|
|
589
|
+
componentLogger.tag = name;
|
|
564
590
|
components.set(name, componentLogger);
|
|
565
591
|
}
|
|
592
|
+
if (isExternal) componentLogger.isExternal = true;
|
|
566
593
|
return componentLogger;
|
|
567
594
|
};
|
|
568
595
|
logger.hasComponent = function (name) {
|
|
569
596
|
return components.has(name);
|
|
570
597
|
};
|
|
598
|
+
logger.components = components;
|
|
571
599
|
}
|
|
572
600
|
return logger;
|
|
573
601
|
}
|
|
@@ -793,13 +821,18 @@ function getLogConfig(hdbConfigPath) {
|
|
|
793
821
|
};
|
|
794
822
|
}
|
|
795
823
|
const configDoc = YAML.parseDocument(fs.readFileSync(hdbConfigPath, 'utf8'));
|
|
824
|
+
const rootPath = configDoc.getIn(['rootPath']);
|
|
796
825
|
const level = configDoc.getIn(['logging', 'level']);
|
|
797
|
-
const configLogPath = configDoc.getIn(['logging', 'root']);
|
|
826
|
+
const configLogPath = resolveLogPath(configDoc.getIn(['logging', 'root']), rootPath);
|
|
798
827
|
const toFile = configDoc.getIn(['logging', 'file']);
|
|
799
828
|
const toStream = configDoc.getIn(['logging', 'stdStreams']);
|
|
800
829
|
const logConsole = configDoc.getIn(['logging', 'console']);
|
|
801
830
|
const colorMode = configDoc.getIn(['logging', 'colors']) ?? true; // default to true
|
|
802
831
|
const rotation = configDoc.getIn(['logging', 'rotation'])?.toJSON();
|
|
832
|
+
// Resolve rotation path if relative
|
|
833
|
+
if (rotation?.path) {
|
|
834
|
+
rotation.path = resolveLogPath(rotation.path, rootPath);
|
|
835
|
+
}
|
|
803
836
|
|
|
804
837
|
return {
|
|
805
838
|
level,
|