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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/bin/cliOperations.js +3 -3
  2. package/bin/harper.js +1 -1
  3. package/bin/run.js +2 -2
  4. package/components/Application.ts +6 -3
  5. package/components/ApplicationScope.ts +1 -0
  6. package/components/componentLoader.ts +23 -12
  7. package/components/operations.js +13 -13
  8. package/components/operationsValidation.js +3 -3
  9. package/config/configUtils.js +20 -4
  10. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +3 -2
  11. package/dist/bin/cliOperations.js +3 -3
  12. package/dist/bin/cliOperations.js.map +1 -1
  13. package/dist/bin/harper.d.ts +1 -1
  14. package/dist/bin/harper.js +1 -1
  15. package/dist/bin/run.js +2 -2
  16. package/dist/bin/run.js.map +1 -1
  17. package/dist/components/Application.js +7 -2
  18. package/dist/components/Application.js.map +1 -1
  19. package/dist/components/ApplicationScope.d.ts +1 -0
  20. package/dist/components/ApplicationScope.js +1 -0
  21. package/dist/components/ApplicationScope.js.map +1 -1
  22. package/dist/components/componentLoader.js +21 -10
  23. package/dist/components/componentLoader.js.map +1 -1
  24. package/dist/components/operations.js +13 -13
  25. package/dist/components/operations.js.map +1 -1
  26. package/dist/components/operationsValidation.js +3 -3
  27. package/dist/components/operationsValidation.js.map +1 -1
  28. package/dist/config/configUtils.d.ts +6 -0
  29. package/dist/config/configUtils.js +23 -3
  30. package/dist/config/configUtils.js.map +1 -1
  31. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +3 -2
  32. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
  33. package/dist/resources/DatabaseTransaction.d.ts +2 -1
  34. package/dist/resources/DatabaseTransaction.js +71 -35
  35. package/dist/resources/DatabaseTransaction.js.map +1 -1
  36. package/dist/resources/Resource.d.ts +2 -0
  37. package/dist/resources/Resource.js +11 -4
  38. package/dist/resources/Resource.js.map +1 -1
  39. package/dist/resources/RocksTransactionLogStore.d.ts +1 -0
  40. package/dist/resources/RocksTransactionLogStore.js +8 -1
  41. package/dist/resources/RocksTransactionLogStore.js.map +1 -1
  42. package/dist/resources/Table.d.ts +6 -6
  43. package/dist/resources/Table.js +15 -10
  44. package/dist/resources/Table.js.map +1 -1
  45. package/dist/resources/analytics/write.js +6 -0
  46. package/dist/resources/analytics/write.js.map +1 -1
  47. package/dist/resources/databases.js +3 -2
  48. package/dist/resources/databases.js.map +1 -1
  49. package/dist/security/jsLoader.js +223 -116
  50. package/dist/security/jsLoader.js.map +1 -1
  51. package/dist/server/REST.js +30 -14
  52. package/dist/server/REST.js.map +1 -1
  53. package/dist/server/http.d.ts +1 -0
  54. package/dist/server/http.js +6 -1
  55. package/dist/server/http.js.map +1 -1
  56. package/dist/server/itc/serverHandlers.js +1 -1
  57. package/dist/server/itc/serverHandlers.js.map +1 -1
  58. package/dist/utility/hdbTerms.d.ts +1 -0
  59. package/dist/utility/hdbTerms.js +1 -0
  60. package/dist/utility/hdbTerms.js.map +1 -1
  61. package/dist/utility/logging/harper_logger.js +24 -1
  62. package/dist/utility/logging/harper_logger.js.map +1 -1
  63. package/dist/utility/logging/readLog.js +2 -2
  64. package/dist/utility/logging/readLog.js.map +1 -1
  65. package/dist/utility/npmUtilities.js +2 -2
  66. package/dist/utility/npmUtilities.js.map +1 -1
  67. package/dist/validation/configValidator.js +18 -8
  68. package/dist/validation/configValidator.js.map +1 -1
  69. package/dist/validation/readLogValidator.js +2 -2
  70. package/dist/validation/readLogValidator.js.map +1 -1
  71. package/package.json +5 -5
  72. package/resources/DatabaseTransaction.ts +67 -32
  73. package/resources/Resource.ts +17 -6
  74. package/resources/RocksTransactionLogStore.ts +8 -1
  75. package/resources/Table.ts +29 -10
  76. package/resources/analytics/write.ts +6 -0
  77. package/resources/databases.ts +3 -2
  78. package/security/jsLoader.ts +258 -129
  79. package/server/REST.ts +32 -13
  80. package/server/http.ts +6 -3
  81. package/server/itc/serverHandlers.js +1 -1
  82. package/static/defaultConfig.yaml +1 -1
  83. package/studio/web/assets/{index-BckVDix4.js → index-CXQsBaYq.js} +5 -5
  84. package/studio/web/assets/{index-BckVDix4.js.map → index-CXQsBaYq.js.map} +1 -1
  85. package/studio/web/assets/{index.lazy-iG1_8dzm.js → index.lazy-C3Ejfvna.js} +2 -2
  86. package/studio/web/assets/{index.lazy-iG1_8dzm.js.map → index.lazy-C3Ejfvna.js.map} +1 -1
  87. package/studio/web/assets/{profile-CzjslUXv.js → profile-BbbbWJCN.js} +2 -2
  88. package/studio/web/assets/{profile-CzjslUXv.js.map → profile-BbbbWJCN.js.map} +1 -1
  89. package/studio/web/assets/{status-BP4TQJDR.js → status-CFe85l8C.js} +2 -2
  90. package/studio/web/assets/{status-BP4TQJDR.js.map → status-CFe85l8C.js.map} +1 -1
  91. package/studio/web/index.html +1 -1
  92. package/utility/hdbTerms.ts +1 -0
  93. package/utility/logging/harper_logger.js +22 -1
  94. package/utility/logging/readLog.js +2 -2
  95. package/utility/npmUtilities.js +2 -2
  96. package/validation/configValidator.js +16 -8
  97. package/validation/readLogValidator.js +2 -2
@@ -56,6 +56,9 @@ export async function scopedImport(filePath: string | URL, scope?: ApplicationSc
56
56
  }
57
57
  overridableProperty(Promise.prototype, 'then');
58
58
  overridableProperty(Date, 'now');
59
+ for (let name of ['get', 'set', 'has', 'delete', 'clear', 'forEach', 'entries', 'keys', 'values']) {
60
+ overridableProperty(Map.prototype, name);
61
+ }
59
62
  for (let Intrinsic of [
60
63
  Object,
61
64
  Array,
@@ -121,12 +124,13 @@ export async function scopedImport(filePath: string | URL, scope?: ApplicationSc
121
124
 
122
125
  let amaro: typeof import('amaro') | undefined;
123
126
  /**
124
- * Strip TypeScript types using the amaro library (what Node.js uses internally)
125
- * Falls back to regex-based stripping if amaro is not available
127
+ * Strip TypeScript types synchronously using the amaro library (what Node.js uses internally)
126
128
  */
127
- async function stripTypeScriptTypes(source: string): Promise<string> {
129
+ function stripTypeScriptTypes(source: string): string {
128
130
  // Use amaro - the library that Node.js uses internally for type stripping
129
- amaro = await import('amaro');
131
+ if (!amaro) {
132
+ amaro = require('amaro');
133
+ }
130
134
  return amaro.transformSync(source, { mode: 'strip-only' }).code;
131
135
  }
132
136
 
@@ -146,12 +150,27 @@ function parseJsonModule(source: string, url: string): any {
146
150
  * Load a module using Node's vm.Module API with (not really secure) sandboxing
147
151
  */
148
152
  async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
149
- const moduleCache = new Map<string, Promise<SourceTextModule | SyntheticModule>>();
150
- const linkingPromises = new Map<string, Promise<void>>();
151
-
152
- // Create a secure context with limited globals
153
- const contextObject = getGlobalObject(scope);
154
- const context = createContext(contextObject);
153
+ // we want to retain the same module caches across any loading with the application scope
154
+ let moduleCaches = scope.moduleCache as {
155
+ moduleCache: Map<string, SourceTextModule | SyntheticModule | Promise<SourceTextModule | SyntheticModule>>;
156
+ linkingPromises: Map<string, Promise<void>>;
157
+ cjsCache: Map<string, { exports: any }>;
158
+ contextObject: any;
159
+ context: any;
160
+ };
161
+ if (!moduleCaches) {
162
+ // if they haven't been initialized, do so now
163
+ const contextObject = getGlobalObject(scope, true);
164
+ moduleCaches = scope.moduleCache = {
165
+ moduleCache: new Map(),
166
+ linkingPromises: new Map(),
167
+ cjsCache: new Map(),
168
+ // Create a secure context with limited globals
169
+ contextObject,
170
+ context: createContext(contextObject),
171
+ };
172
+ }
173
+ const { moduleCache, linkingPromises, cjsCache, contextObject, context } = moduleCaches;
155
174
 
156
175
  /**
157
176
  * Resolve module specifier to absolute URL
@@ -165,6 +184,9 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
165
184
  // block harper/* for now (reserving for potential future use)
166
185
  throw new Error(`Module ${specifier} is not allowed, may only access the 'harper' module`);
167
186
  }
187
+ if (parts[0] === 'file:') {
188
+ return specifier;
189
+ }
168
190
  const resolved = createRequire(referrer).resolve(specifier);
169
191
  if (isAbsolute(resolved)) {
170
192
  return pathToFileURL(resolved).toString();
@@ -176,7 +198,16 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
176
198
  * Load a CommonJS module in our private context
177
199
  */
178
200
  function loadCJS(url: string, source: string): { exports: any } {
201
+ // Check cache first to handle circular dependencies
202
+ if (cjsCache.has(url)) {
203
+ return cjsCache.get(url)!;
204
+ }
205
+
206
+ // Create module object and cache it immediately (before execution)
207
+ // This allows circular dependencies to get a reference to the incomplete module
179
208
  const cjsModule = { exports: {} };
209
+ cjsCache.set(url, cjsModule);
210
+
180
211
  if (url.endsWith('.json')) {
181
212
  cjsModule.exports = parseJsonModule(source, url);
182
213
  return cjsModule;
@@ -204,7 +235,7 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
204
235
  filename: url,
205
236
  async importModuleDynamically(specifier: string, script) {
206
237
  const resolvedUrl = resolveModule(specifier, script.sourceURL);
207
- const useContainment = specifier.startsWith('.') || scope.dependencyContainment;
238
+ const useContainment = specifier.startsWith('.') || scope.dependencyContainment !== false;
208
239
  const dynamicModule = await loadModuleWithCache(resolvedUrl, useContainment);
209
240
  return dynamicModule;
210
241
  },
@@ -221,16 +252,18 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
221
252
  }
222
253
  function loadCJSModule(url: string, source: string, usePrivateGlobal: boolean): SyntheticModule {
223
254
  const cjsModule = usePrivateGlobal ? loadCJS(url, source) : { exports: require(url) };
224
- const exportNames = Object.keys(cjsModule.exports);
255
+ let exports = cjsModule.exports;
256
+ if (exports.default === undefined) {
257
+ // provide the default export for compatibility
258
+ exports = { default: exports, ...exports };
259
+ }
260
+ const exportNames = Object.keys(exports);
261
+
225
262
  const synModule = new SyntheticModule(
226
- exportNames.length > 0 ? exportNames : ['default'],
263
+ exportNames,
227
264
  function () {
228
- if (exportNames.length > 0) {
229
- for (const key of exportNames) {
230
- this.setExport(key, cjsModule.exports[key]);
231
- }
232
- } else {
233
- this.setExport('default', cjsModule.exports);
265
+ for (const key of exportNames) {
266
+ this.setExport(key, exports[key]);
234
267
  }
235
268
  },
236
269
  { identifier: url, context }
@@ -240,21 +273,78 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
240
273
  }
241
274
 
242
275
  /**
243
- * Linker function for module resolution during instantiation
276
+ * Check if a package (or any of its dependencies) depends on harper
277
+ * Expects a file URL like: file:///path/to/node_modules/package-name/dist/index.js
278
+ */
279
+ function packageDependsOnHarper(fileUrl: string): boolean {
280
+ try {
281
+ // Convert file:// URL to path
282
+ const filePath = fileURLToPath(fileUrl);
283
+
284
+ // Find the node_modules directory and package name
285
+ // Example: /path/to/node_modules/package-name/dist/index.js
286
+ // or: /path/to/node_modules/@scope/package-name/dist/index.js
287
+ const nodeModulesMarker = '/node_modules/';
288
+ const nodeModulesIndex = filePath.lastIndexOf(nodeModulesMarker);
289
+ if (nodeModulesIndex === -1) return false;
290
+
291
+ // Get the part after /node_modules/
292
+ const afterNodeModules = filePath.substring(nodeModulesIndex + nodeModulesMarker.length);
293
+ const parts = afterNodeModules.split('/');
294
+
295
+ // Handle scoped packages (@scope/package-name) vs regular packages (package-name)
296
+ const beforeNodeModules = filePath.substring(0, nodeModulesIndex);
297
+ const packageRoot = parts[0].startsWith('@')
298
+ ? join(beforeNodeModules, 'node_modules', parts[0], parts[1])
299
+ : join(beforeNodeModules, 'node_modules', parts[0]);
300
+
301
+ // Read package.json from the package root
302
+ const packageJsonPath = join(packageRoot, 'package.json');
303
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
304
+
305
+ const deps = {
306
+ ...packageJson.dependencies,
307
+ ...packageJson.devDependencies,
308
+ ...packageJson.peerDependencies,
309
+ };
310
+
311
+ // Check if harper is a direct dependency
312
+ return Object.keys(deps).some((dep) => HARPER_MODULE_IDS.has(dep));
313
+ } catch {
314
+ return false;
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Linker function for module resolution during instantiation.
320
+ * This is synchronous because Node's module.link() requires the linker
321
+ * to return modules synchronously.
244
322
  */
245
- async function linker(specifier: string, referencingModule: SourceTextModule | SyntheticModule) {
323
+ function linker(specifier: string, referencingModule: SourceTextModule | SyntheticModule) {
246
324
  const resolvedUrl = resolveModule(specifier, referencingModule.identifier);
247
325
 
248
- const useContainment = specifier.startsWith('.') || scope.dependencyContainment;
249
- // Return the module immediately (even if not yet linked) to support circular dependencies
250
- return await getOrCreateModule(resolvedUrl, useContainment);
326
+ // Determine if we should use VM containment for this module
327
+ let useContainment = specifier.startsWith('.'); // Always contain relative imports
328
+
329
+ if (!useContainment && scope.dependencyContainment !== false) {
330
+ // For npm packages, check if they depend on harper
331
+ if (resolvedUrl.startsWith('file://') && resolvedUrl.includes('node_modules')) {
332
+ useContainment = packageDependsOnHarper(resolvedUrl);
333
+ } else {
334
+ // Non-file URLs (bare specifiers) - use default behavior
335
+ useContainment = scope.dependencyContainment === true;
336
+ }
337
+ }
338
+
339
+ // Return the module
340
+ return getOrCreateModule(resolvedUrl, useContainment);
251
341
  }
252
342
 
253
- async function getOrCreateModule(
343
+ function getOrCreateModule(
254
344
  url: string,
255
345
  usePrivateGlobal: boolean
256
- ): Promise<SourceTextModule | SyntheticModule> {
257
- // Check cache first - return cached module immediately (even if not linked yet)
346
+ ): SourceTextModule | SyntheticModule | Promise<SourceTextModule | SyntheticModule> {
347
+ // Check if module is already created
258
348
  if (moduleCache.has(url)) {
259
349
  return moduleCache.get(url)!;
260
350
  }
@@ -275,8 +365,15 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
275
365
  // Only link/evaluate once per module
276
366
  if (!linkingPromises.has(url)) {
277
367
  const linkingPromise = (async () => {
278
- await module.link(linker);
279
- await module.evaluate();
368
+ // Check module status - only link if it's 'unlinked'
369
+ // Status can be: 'unlinked', 'linking', 'linked', 'evaluating', 'evaluated'
370
+ if (module.status === 'unlinked') {
371
+ await module.link(linker);
372
+ }
373
+ // Only evaluate if not already evaluated
374
+ if (module.status === 'linked') {
375
+ await module.evaluate();
376
+ }
280
377
  })();
281
378
  linkingPromises.set(url, linkingPromise);
282
379
  }
@@ -287,107 +384,107 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope) {
287
384
  return module;
288
385
  }
289
386
  /**
290
- * Create a module from URL without linking or evaluating
387
+ * Create a SyntheticModule from exported object
291
388
  */
292
- async function createModule(url: string, usePrivateGlobal: boolean): Promise<SourceTextModule | SyntheticModule> {
293
- let module: SourceTextModule | SyntheticModule;
389
+ function createSyntheticModule(url: string, exportedObject: any): SyntheticModule {
390
+ const exportNames = Object.keys(exportedObject);
391
+ return new SyntheticModule(
392
+ exportNames,
393
+ function () {
394
+ for (const key of exportNames) {
395
+ this.setExport(key, exportedObject[key]);
396
+ }
397
+ },
398
+ { identifier: url, context }
399
+ );
400
+ }
294
401
 
295
- // Handle special built-in modules
296
- if (url === 'harper') {
297
- let harperExports = getHarperExports(scope);
298
- module = new SyntheticModule(
299
- Object.keys(harperExports),
402
+ /**
403
+ * Normalize imported module to ensure it has proper exports including default
404
+ */
405
+ function normalizeImportedModule(importedModule: any): any {
406
+ const cjsModule = importedModule['module.exports'];
407
+ if (cjsModule) {
408
+ // back-compat import
409
+ importedModule = importedModule.default ? { default: importedModule.default, ...cjsModule } : cjsModule;
410
+ }
411
+ // Ensure there's a default export for ESM imports that expect it
412
+ if (!importedModule.default) {
413
+ importedModule = { default: importedModule, ...importedModule };
414
+ }
415
+ return importedModule;
416
+ }
417
+
418
+ /**
419
+ * Create a SourceTextModule or SyntheticModule from source code
420
+ */
421
+ function createModuleFromSource(
422
+ url: string,
423
+ source: string,
424
+ usePrivateGlobal: boolean
425
+ ): SourceTextModule | SyntheticModule {
426
+ // Handle JSON modules
427
+ if (url.endsWith('.json')) {
428
+ const jsonData = parseJsonModule(source, url);
429
+ return new SyntheticModule(
430
+ ['default'],
300
431
  function () {
301
- for (let key in harperExports) {
302
- this.setExport(key, harperExports[key]);
303
- }
432
+ this.setExport('default', jsonData);
304
433
  },
305
434
  { identifier: url, context }
306
435
  );
307
- } else if (url.startsWith('file://') && usePrivateGlobal) {
308
- checkAllowedModulePath(url, scope.verifyPath);
309
- let source = await readFile(new URL(url), { encoding: 'utf-8' });
310
-
311
- // Handle JSON modules as a SyntheticModule with a default export.
312
- // JSON imports only support default exports per the ESM spec.
313
- if (url.endsWith('.json')) {
314
- const jsonData = parseJsonModule(source, url);
315
- module = new SyntheticModule(
316
- ['default'],
317
- function () {
318
- this.setExport('default', jsonData);
319
- },
320
- { identifier: url, context }
321
- );
322
- } else {
323
- // Strip TypeScript types if this is a .ts file
324
- if (url.endsWith('.ts') || url.endsWith('.tsx')) {
325
- source = await stripTypeScriptTypes(source);
326
- }
436
+ }
327
437
 
328
- // Try CJS first since it will fail fast with clear syntax errors on ESM syntax
329
- try {
330
- module = loadCJSModule(url, source, usePrivateGlobal);
331
- } catch {
332
- // If CJS loading fails (likely due to ESM syntax like import/export), try ESM
333
- try {
334
- module = new SourceTextModule(source, {
335
- identifier: url,
336
- context,
337
- initializeImportMeta(meta) {
338
- meta.url = url;
339
- },
340
- async importModuleDynamically(specifier: string) {
341
- const resolvedUrl = resolveModule(specifier, url);
342
- const dynamicModule = await loadModuleWithCache(resolvedUrl, true);
343
- return dynamicModule;
344
- },
345
- });
346
- } catch (esmErr) {
347
- // Both failed - throw the ESM error as it's likely more relevant
348
- throw esmErr;
349
- }
350
- }
351
- }
352
- } else {
353
- const replacedModule = checkAllowedModulePath(url, scope.verifyPath);
354
- // For Node.js built-in modules (node:) and npm packages
355
- // Always try require first to properly handle CJS modules with named exports
356
- try {
357
- const cjsExports = replacedModule ?? require(url);
358
- // It's a CJS module - expose all properties as named exports
359
- const exportNames = Object.keys(cjsExports);
360
- module = new SyntheticModule(
361
- exportNames.length > 0 ? [...exportNames, 'default'] : ['default'],
362
- function () {
363
- if (exportNames.length > 0) {
364
- for (const key of exportNames) {
365
- this.setExport(key, cjsExports[key]);
366
- }
367
- }
368
- this.setExport('default', cjsExports);
369
- },
370
- { identifier: url, context }
371
- );
372
- } catch {
373
- // Fall back to dynamic import for ESM packages
374
- const importedModule = await import(url);
375
- const exportNames = Object.keys(importedModule);
376
- module = new SyntheticModule(
377
- exportNames,
378
- function () {
379
- for (const key of exportNames) {
380
- this.setExport(key, importedModule[key]);
381
- }
382
- },
383
- { identifier: url, context }
384
- );
385
- }
438
+ // Strip TypeScript types if this is a .ts file
439
+ if (url.endsWith('.ts') || url.endsWith('.tsx')) {
440
+ source = stripTypeScriptTypes(source);
386
441
  }
387
442
 
388
- return module;
443
+ // Try CJS first since it will fail fast with clear syntax errors on ESM syntax
444
+ try {
445
+ return loadCJSModule(url, source, usePrivateGlobal);
446
+ } catch {
447
+ // If CJS loading fails (likely due to ESM syntax like import/export), try ESM
448
+ return new SourceTextModule(source, {
449
+ identifier: url,
450
+ context,
451
+ initializeImportMeta(meta) {
452
+ meta.url = url;
453
+ },
454
+ importModuleDynamically(specifier: string) {
455
+ const resolvedUrl = resolveModule(specifier, url);
456
+ const useContainment = specifier.startsWith('.') || scope.dependencyContainment !== false;
457
+ return loadModuleWithCache(resolvedUrl, useContainment);
458
+ },
459
+ });
460
+ }
389
461
  }
390
462
 
463
+ /**
464
+ * Create a module from URL without linking or evaluating (async version for initial load)
465
+ */
466
+ function createModule(
467
+ url: string,
468
+ usePrivateGlobal: boolean
469
+ ): SourceTextModule | SyntheticModule | Promise<SourceTextModule | SyntheticModule> {
470
+ // Handle special built-in modules
471
+ if (url === 'harper') {
472
+ return createSyntheticModule(url, getHarperExports(scope));
473
+ }
474
+
475
+ if (url.startsWith('file://') && usePrivateGlobal) {
476
+ checkAllowedModulePath(url, scope.verifyPath);
477
+ const source = readFileSync(new URL(url), { encoding: 'utf-8' });
478
+ return createModuleFromSource(url, source, usePrivateGlobal);
479
+ }
480
+
481
+ // For Node.js built-in modules (node:) and npm packages without dependency containment
482
+ const replacedModule = checkAllowedModulePath(url, scope.verifyPath);
483
+ if (replacedModule) {
484
+ return createSyntheticModule(url, normalizeImportedModule(replacedModule));
485
+ }
486
+ return import(url).then((importedModule) => createSyntheticModule(url, normalizeImportedModule(importedModule)));
487
+ }
391
488
  // Load the entry module
392
489
  const entryModule = await loadModuleWithCache(moduleUrl, true);
393
490
 
@@ -433,8 +530,8 @@ async function getCompartment(scope: ApplicationScope, globals) {
433
530
  },
434
531
  };
435
532
  } else if (moduleSpecifier.startsWith('file:') && !moduleSpecifier.includes('node_modules')) {
436
- const moduleText = await readFile(new URL(moduleSpecifier), { encoding: 'utf-8' });
437
- // Handle JSON files in comparttment mode the same way as in VM mode
533
+ let moduleText = await readFile(new URL(moduleSpecifier), { encoding: 'utf-8' });
534
+ // Handle JSON files in compartment mode the same way as in VM mode
438
535
  if (moduleSpecifier.endsWith('.json')) {
439
536
  const jsonData = parseJsonModule(moduleText, moduleSpecifier);
440
537
  return {
@@ -445,6 +542,10 @@ async function getCompartment(scope: ApplicationScope, globals) {
445
542
  },
446
543
  };
447
544
  }
545
+ // Strip TypeScript types if this is a .ts file
546
+ if (moduleSpecifier.endsWith('.ts') || moduleSpecifier.endsWith('.tsx')) {
547
+ moduleText = stripTypeScriptTypes(moduleText);
548
+ }
448
549
  return new StaticModuleRecord(moduleText, moduleSpecifier);
449
550
  } else {
450
551
  checkAllowedModulePath(moduleSpecifier, scope.verifyPath);
@@ -479,6 +580,9 @@ function secureOnlyFetch(resource, options) {
479
580
  return fetch(resource, options);
480
581
  }
481
582
 
583
+ // These globals need to match the literals produced in the VM context
584
+ const contextualizedJSGlobals = ['Object', 'Array', 'Function', 'globalThis'];
585
+
482
586
  let defaultJSGlobalNames: string[];
483
587
  // get the global variable names that are intrinsically present in a VM context (so we don't override them)
484
588
  function getDefaultJSGlobalNames() {
@@ -494,12 +598,13 @@ function getDefaultJSGlobalNames() {
494
598
  /**
495
599
  * Get the set of global variables that should be available to modules that run in scoped compartments/contexts.
496
600
  */
497
- function getGlobalObject(scope: ApplicationScope) {
601
+ function getGlobalObject(scope: ApplicationScope, copyIntrinsics = false) {
498
602
  const appGlobal = {};
499
603
  // create the new global object, assigning all the global variables from this global
500
604
  // except those that will be natural intrinsics of the new VM
605
+ const globalsToExclude = copyIntrinsics ? contextualizedJSGlobals : getDefaultJSGlobalNames();
501
606
  for (let name of Object.getOwnPropertyNames(global)) {
502
- if (getDefaultJSGlobalNames().includes(name)) continue;
607
+ if (globalsToExclude.includes(name)) continue;
503
608
  appGlobal[name] = global[name];
504
609
  }
505
610
  // now assign Harper scope-specific variables
@@ -533,6 +638,25 @@ function getHarperExports(scope: ApplicationScope) {
533
638
  authenticateUser: server.authenticateUser,
534
639
  operation: server.operation,
535
640
  contentTypes,
641
+ Attribute: undefined,
642
+ Config: undefined,
643
+ ConfigValue: undefined,
644
+ Context: undefined,
645
+ FileAndURLPathConfig: undefined,
646
+ FilesOption: undefined,
647
+ FilesOptionObject: undefined,
648
+ IterableEventQueue: undefined,
649
+ Logger: undefined,
650
+ Query: undefined,
651
+ RecordObject: undefined,
652
+ RequestTargetOrId: undefined,
653
+ ResourceInterface: undefined,
654
+ Scope: undefined,
655
+ Session: undefined,
656
+ SourceContext: undefined,
657
+ SubscriptionRequest: undefined,
658
+ Table: undefined,
659
+ User: undefined,
536
660
  };
537
661
  }
538
662
  const ALLOWED_NODE_BUILTIN_MODULES = env.get(CONFIG_PARAMS.APPLICATIONS_ALLOWEDBUILTINMODULES)
@@ -544,14 +668,19 @@ const ALLOWED_NODE_BUILTIN_MODULES = env.get(CONFIG_PARAMS.APPLICATIONS_ALLOWEDB
544
668
  },
545
669
  };
546
670
  const ALLOWED_COMMANDS = new Set(env.get(CONFIG_PARAMS.APPLICATIONS_ALLOWEDSPAWNCOMMANDS) ?? []);
547
- const REPLACED_BUILTIN_MODULES = {
548
- child_process: {
549
- exec: createSpawn(child_process.exec),
550
- execFile: createSpawn(child_process.execFile),
551
- fork: createSpawn(child_process.fork, true), // this is launching node, so deemed safe
552
- spawn: createSpawn(child_process.spawn),
671
+ const child_processConstrained = {
672
+ exec: createSpawn(child_process.exec),
673
+ execFile: createSpawn(child_process.execFile),
674
+ fork: createSpawn(child_process.fork, true), // this is launching node, so deemed safe
675
+ spawn: createSpawn(child_process.spawn),
676
+ execSync: function () {
677
+ throw new Error('execSync is not allowed');
553
678
  },
554
679
  };
680
+ child_processConstrained.default = child_processConstrained;
681
+ const REPLACED_BUILTIN_MODULES = {
682
+ child_process: child_processConstrained,
683
+ };
555
684
  /**
556
685
  * Creates a ChildProcess-like object for an existing process
557
686
  */
package/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('Allow', 'GET, HEAD, POST, PUT, DELETE, PATCH, OPTIONS, TRACE, QUERY, COPY, MOVE');
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(target, headersObject.destination, request);
142
+ return resource.copy
143
+ ? resource.copy(target, headersObject.destination, request)
144
+ : missingMethod(resource, 'copy');
138
145
  case 'MOVE':
139
- return resource.move(target, headersObject.destination, request);
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:
@@ -164,8 +173,18 @@ async function http(request: Context & Request, nextHandler) {
164
173
  responseData.headers = responseHeaders;
165
174
  // if no body, look for provided data to serialize
166
175
  if (!responseData.body) {
167
- if ('data' in responseData) responseData.body = serialize(responseData.data, request, responseData);
168
- else responseData.body = serialize(responseData, request, responseData);
176
+ let body: any;
177
+ if ('data' in responseData) {
178
+ // a standard Response object does not have a setter for body, so we force it
179
+ body = serialize(responseData.data, request, responseData);
180
+ } else if (responseData.body === undefined) {
181
+ // if there is really no body, serialize this object into the body. Note that `new Response()` creates a response
182
+ // with a null body, and will not fall into this branch
183
+ body = serialize(responseData, request, responseData);
184
+ }
185
+ if (body) {
186
+ responseData = { status: responseData.status, headers: responseData.headers, body };
187
+ }
169
188
  }
170
189
  responseData.status ??= status ?? 200;
171
190
  return responseData;
package/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 { resolvePath } from '../config/configUtils.js';
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';
@@ -35,6 +35,7 @@ const httpServers = {},
35
35
  httpChain = {},
36
36
  httpResponders = [];
37
37
  let httpOptions: HttpOptions = {};
38
+ export const universalHeaders: [string, string][] = [];
38
39
 
39
40
  export function handleApplication(scope: Scope) {
40
41
  httpOptions = scope.options.getAll() as HttpOptions;
@@ -181,7 +182,7 @@ function getPorts(options) {
181
182
 
182
183
  if (options?.usageType === 'operations-api' && env.get(terms.CONFIG_PARAMS.OPERATIONSAPI_NETWORK_DOMAINSOCKET)) {
183
184
  ports.push({
184
- port: resolvePath(env.get(terms.CONFIG_PARAMS.OPERATIONSAPI_NETWORK_DOMAINSOCKET)),
185
+ port: getConfigPath(terms.CONFIG_PARAMS.OPERATIONSAPI_NETWORK_DOMAINSOCKET),
185
186
  secure: false,
186
187
  });
187
188
  }
@@ -269,7 +270,9 @@ function getHTTPServer(port: number, secure: boolean, options: ServerOptions) {
269
270
  if (!response.headers?.set) {
270
271
  response.headers = new Headers(response.headers);
271
272
  }
272
-
273
+ for (let [key, value] of universalHeaders) {
274
+ response.headers.set(key, value);
275
+ }
273
276
  if (response.status === -1) {
274
277
  // This means the HDB stack didn't handle the request, and we can then cascade the request
275
278
  // to the server-level handler, forming the bridge to the slower legacy fastify framework that expects
@@ -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.warn(`Failed to send direct response to thread ${originatorThreadId}, falling back to broadcast`);
152
+ hdbLogger.debug(`Failed to send direct response to thread ${originatorThreadId}, falling back to broadcast`);
153
153
  }
154
154
  await sendItcEvent(responseMessage);
155
155
  }
@@ -26,7 +26,7 @@ analytics:
26
26
  applications:
27
27
  lockdown: freeze
28
28
  containment: vm
29
- dependencyContainment: false
29
+ dependencyContainment: auto
30
30
  allowedSpawnCommands:
31
31
  - npm
32
32
  - node