"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
    if (typeof path === "string" && /^\.\.?\//.test(path)) {
        return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
            return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
        });
    }
    return path;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.scopedImport = scopedImport;
exports.preventFunctionConstructor = preventFunctionConstructor;
const Resource_ts_1 = require("../resources/Resource.js");
const transaction_ts_1 = require("../resources/transaction.js");
const RequestTarget_ts_1 = require("../resources/RequestTarget.js");
const databases_ts_1 = require("../resources/databases.js");
const promises_1 = require("node:fs/promises");
const node_path_1 = require("node:path");
const node_url_1 = require("node:url");
const node_vm_1 = require("node:vm");
const harper_logger_js_1 = __importDefault(require("../utility/logging/harper_logger.js"));
const node_module_1 = require("node:module");
const env = __importStar(require("../utility/environment/environmentManager"));
const child_process = __importStar(require("node:child_process"));
const hdbTerms_ts_1 = require("../utility/hdbTerms.js");
const contentTypes_ts_1 = require("../server/serverHelpers/contentTypes.js");
const node_fs_1 = require("node:fs");
const node_path_2 = require("node:path");
const node_events_1 = require("node:events");
const threadServer_js_1 = require("../server/threads/threadServer.js");
const APPLICATIONS_LOCKDOWN = env.get(hdbTerms_ts_1.CONFIG_PARAMS.APPLICATIONS_LOCKDOWN);
const HARPER_MODULE_IDS = new Set([
    'harper',
    'harperdb',
    'harperdb/v1',
    'harperdb/v2',
    '@harperfast/harper',
    '@harperfast/harper-pro',
]);
let lockedDown = false;
/**
 * This is the main entry point for loading plugin and application modules that may be executed in a
 * separate top level scope. The scope indicates if we use a different top level scope or a standard import.
 * @param moduleUrl
 * @param scope
 */
async function scopedImport(filePath, scope) {
    if (!lockedDown && APPLICATIONS_LOCKDOWN && APPLICATIONS_LOCKDOWN !== 'none') {
        lockedDown = true;
        if (APPLICATIONS_LOCKDOWN === 'ses') {
            require('ses'); // load the lockdown function
            lockdown({
                domainTaming: 'unsafe',
                consoleTaming: 'unsafe',
                errorTaming: 'unsafe',
                errorTrapping: 'none',
                stackFiltering: 'verbose',
            });
        }
        else {
            preventFunctionConstructor();
            if (APPLICATIONS_LOCKDOWN === 'freeze-after-load') {
                threadServer_js_1.whenComponentsLoaded.then(freezeIntrinsics);
            }
            else {
                freezeIntrinsics();
            }
        }
    }
    const moduleUrl = (filePath instanceof URL ? filePath : (0, node_url_1.pathToFileURL)(filePath)).toString();
    try {
        const loaderMode = scope?.mode;
        if (scope && loaderMode !== 'native') {
            if (loaderMode === 'compartment') {
                // use SES Compartments
                // note that we use a single compartment per scope and we load it on-demand, only
                // loading if necessary (since it is actually very heavy)
                const globals = getGlobalObject(scope);
                if (!scope.compartment)
                    scope.compartment = getCompartment(scope, globals);
                const result = await (await scope.compartment).import(moduleUrl);
                return result.namespace;
            }
            else if (loaderMode === 'vm-current-context' && node_vm_1.SourceTextModule) {
                // Use VM module loader with current context (shares intrinsics, no custom globals)
                return await loadModuleWithVM(moduleUrl, scope, false);
            }
            else if (node_vm_1.SourceTextModule) {
                // Use VM module to do module loading with custom context (sandboxed)
                return await loadModuleWithVM(moduleUrl, scope, true);
            }
        }
        // important! we need to await the import, otherwise the error will not be caught
        return await import(__rewriteRelativeImportExtension(moduleUrl));
    }
    catch (err) {
        try {
            // the actual parse error (internally known as the "arrow message")
            // is hidden behind a private symbol (arrowMessagePrivateSymbol)
            // on the error object and the only way to access it is to use the
            // internal util.decorateErrorStack() function
            const util = await import('internal/util');
            util.default.decorateErrorStack(err);
        }
        catch {
            // maybe --expose-internals was not set?
        }
        throw err;
    }
}
let amaro;
/**
 * Strip TypeScript types synchronously using the amaro library (what Node.js uses internally)
 */
function stripTypeScriptTypes(source) {
    // Use amaro - the library that Node.js uses internally for type stripping
    if (!amaro) {
        amaro = require('amaro');
    }
    return amaro.transformSync(source, { mode: 'strip-only' }).code;
}
/**
 * Parse a JSON string and return the resulting object. Wraps JSON.parse errors
 * with the module URL for easier debugging.
 */
function parseJsonModule(source, url) {
    try {
        return JSON.parse(source);
    }
    catch (err) {
        throw new Error(`Failed to parse JSON module ${url}: ${err.message}`);
    }
}
/**
 * Load a module using Node's vm.Module API with optional sandboxing
 * @param moduleUrl - The URL of the module to load
 * @param scope - The application scope
 * @param useCustomContext - If true, uses a sandboxed context with custom globals. If false, uses current context.
 */
async function loadModuleWithVM(moduleUrl, scope, useCustomContext = true) {
    // we want to retain the same module caches across any loading with the application scope
    let moduleCaches = scope.moduleCache;
    if (!moduleCaches) {
        // if they haven't been initialized, do so now
        let contextObject, context;
        if (useCustomContext) {
            // Create a sandboxed context with custom globals
            contextObject = getGlobalObject(scope, true);
            context = (0, node_vm_1.createContext)(contextObject);
        }
        else {
            // Use current context (no sandboxing, shares intrinsics with parent)
            contextObject = undefined;
            context = undefined;
        }
        moduleCaches = scope.moduleCache = {
            moduleCache: new Map(),
            linkingPromises: new Map(),
            cjsCache: new Map(),
            contextObject,
            context,
        };
    }
    const { moduleCache, linkingPromises, cjsCache, contextObject, context } = moduleCaches;
    /**
     * Resolve module specifier to absolute URL
     */
    function resolveModule(specifier, referrer) {
        if (HARPER_MODULE_IDS.has(specifier)) {
            return 'harper'; // resolve any harper package as an alias to a single synthetic module
        }
        const parts = specifier.split('/');
        if (parts[0] === 'harper') {
            // block harper/* for now (reserving for potential future use)
            throw new Error(`Module ${specifier} is not allowed, may only access the 'harper' module`);
        }
        if (parts[0] === 'file:') {
            return specifier;
        }
        const resolved = (0, node_module_1.createRequire)(referrer).resolve(specifier);
        if ((0, node_path_1.isAbsolute)(resolved)) {
            return (0, node_url_1.pathToFileURL)(resolved).toString();
        }
        return resolved;
    }
    /**
     * Load a CommonJS module in our context (private or current)
     */
    function loadCJS(url, source) {
        // Check cache first to handle circular dependencies
        if (cjsCache.has(url)) {
            return cjsCache.get(url);
        }
        // Create module object and cache it immediately (before execution)
        // This allows circular dependencies to get a reference to the incomplete module
        const cjsModule = { exports: {} };
        cjsCache.set(url, cjsModule);
        if (url.endsWith('.json')) {
            cjsModule.exports = parseJsonModule(source, url);
            return cjsModule;
        }
        const require = (0, node_module_1.createRequire)(url);
        const cjsRequire = (spec) => {
            const resolvedPath = require.resolve(spec);
            if ((0, node_path_1.isAbsolute)(resolvedPath)) {
                const source = (0, node_fs_1.readFileSync)(resolvedPath, { encoding: 'utf-8' });
                return loadCJS(resolvedPath, source).exports;
            }
            else {
                return require(spec);
            }
        };
        cjsRequire.resolve = require.resolve;
        const cjsWrapper = `
			(function(module, exports, require, __filename, __dirname) {
				${source}
			})
		`;
        const runOptions = {
            filename: url,
            async importModuleDynamically(specifier, script) {
                const resolvedUrl = resolveModule(specifier, script.sourceURL);
                const useApplicationLoader = shouldUseApplicationLoader(specifier, resolvedUrl);
                const dynamicModule = await loadModuleWithCache(resolvedUrl, useApplicationLoader);
                return dynamicModule;
            },
        };
        // If contextObject is undefined, use current context with runInThisContext
        const wrappedFn = contextObject
            ? (0, node_vm_1.runInContext)(cjsWrapper, contextObject, runOptions)
            : (0, node_vm_1.runInThisContext)(cjsWrapper, runOptions);
        wrappedFn(cjsModule, cjsModule.exports, cjsRequire, url, (0, node_path_1.dirname)(url.startsWith('file://') ? (0, node_url_1.fileURLToPath)(url) : url));
        return cjsModule;
    }
    function loadCJSModule(url, source, usePrivateGlobal) {
        const cjsModule = usePrivateGlobal ? loadCJS(url, source) : { exports: require(url) };
        let exports = cjsModule.exports;
        if (exports.default === undefined) {
            // provide the default export for compatibility
            exports = { default: exports, ...exports };
        }
        const exportNames = Object.keys(exports);
        const options = { identifier: url };
        if (usePrivateGlobal && context) {
            options.context = context;
        }
        const synModule = new node_vm_1.SyntheticModule(exportNames, function () {
            for (const key of exportNames) {
                this.setExport(key, exports[key]);
            }
        }, options);
        // Don't cache here - let getOrCreateModule handle caching
        return synModule;
    }
    /**
     * Check if a package (or any of its dependencies) depends on harper
     * Expects a file URL like: file:///path/to/node_modules/package-name/dist/index.js
     */
    function packageDependsOnHarper(fileUrl) {
        try {
            // Convert file:// URL to path
            const filePath = (0, node_url_1.fileURLToPath)(fileUrl);
            // Find the node_modules directory and package name
            // Example: /path/to/node_modules/package-name/dist/index.js
            // or: /path/to/node_modules/@scope/package-name/dist/index.js
            const nodeModulesMarker = '/node_modules/';
            const nodeModulesIndex = filePath.lastIndexOf(nodeModulesMarker);
            if (nodeModulesIndex === -1)
                return false;
            // Get the part after /node_modules/
            const afterNodeModules = filePath.substring(nodeModulesIndex + nodeModulesMarker.length);
            const parts = afterNodeModules.split('/');
            // Handle scoped packages (@scope/package-name) vs regular packages (package-name)
            const beforeNodeModules = filePath.substring(0, nodeModulesIndex);
            const packageRoot = parts[0].startsWith('@')
                ? (0, node_path_2.join)(beforeNodeModules, 'node_modules', parts[0], parts[1])
                : (0, node_path_2.join)(beforeNodeModules, 'node_modules', parts[0]);
            // Read package.json from the package root
            const packageJsonPath = (0, node_path_2.join)(packageRoot, 'package.json');
            const packageJson = JSON.parse((0, node_fs_1.readFileSync)(packageJsonPath, 'utf-8'));
            const deps = {
                ...packageJson.dependencies,
                ...packageJson.devDependencies,
                ...packageJson.peerDependencies,
            };
            // Check if harper is a direct dependency
            return Object.keys(deps).some((dep) => HARPER_MODULE_IDS.has(dep));
        }
        catch {
            return false;
        }
    }
    /**
     * Determine if a module should use application VM module loader based on specifier and resolved URL
     */
    function shouldUseApplicationLoader(specifier, resolvedUrl) {
        // Always contain relative imports
        if (specifier.startsWith('.')) {
            return true;
        }
        // Check the dependencyLoader for definitive settings, if it is native we always use native loader
        if (scope.dependencyLoader === 'native') {
            return false;
        }
        // If it is set to always use the app module loader
        if (scope.dependencyLoader === 'app') {
            return true;
        }
        // For npm packages, check if they depend on harper
        if (resolvedUrl.startsWith('file://') && resolvedUrl.includes('/node_modules/')) {
            return packageDependsOnHarper(resolvedUrl);
        }
        return false;
    }
    /**
     * Linker function for module resolution during instantiation.
     * This is synchronous because Node's module.link() requires the linker
     * to return modules synchronously.
     */
    function linker(specifier, referencingModule) {
        const resolvedUrl = resolveModule(specifier, referencingModule.identifier);
        const useApplicationLoader = shouldUseApplicationLoader(specifier, resolvedUrl);
        return getOrCreateModule(resolvedUrl, useApplicationLoader);
    }
    function getOrCreateModule(url, usePrivateGlobal) {
        // Check if module is already created
        if (moduleCache.has(url)) {
            return moduleCache.get(url);
        }
        // Create the module and cache it immediately (before linking)
        const module = createModule(url, usePrivateGlobal);
        moduleCache.set(url, module);
        return module;
    }
    async function loadModuleWithCache(url, usePrivateGlobal) {
        const module = await getOrCreateModule(url, usePrivateGlobal);
        // Only link/evaluate once per module
        if (!linkingPromises.has(url)) {
            const linkingPromise = (async () => {
                // Check module status - only link if it's 'unlinked'
                // Status can be: 'unlinked', 'linking', 'linked', 'evaluating', 'evaluated'
                if (module.status === 'unlinked') {
                    await module.link(linker);
                }
                // Only evaluate if not already evaluated
                if (module.status === 'linked') {
                    await module.evaluate();
                }
            })();
            linkingPromises.set(url, linkingPromise);
        }
        // Wait for linking to complete
        await linkingPromises.get(url);
        return module;
    }
    /**
     * Create a SyntheticModule from exported object
     */
    function createSyntheticModule(url, exportedObject) {
        const exportNames = Object.keys(exportedObject);
        return new node_vm_1.SyntheticModule(exportNames, function () {
            for (const key of exportNames) {
                this.setExport(key, exportedObject[key]);
            }
        }, { identifier: url, context });
    }
    /**
     * Normalize imported module to ensure it has proper exports including default
     */
    function normalizeImportedModule(importedModule) {
        const cjsModule = importedModule['module.exports'];
        if (cjsModule) {
            // back-compat import
            importedModule = importedModule.default ? { default: importedModule.default, ...cjsModule } : cjsModule;
        }
        // Ensure there's a default export for ESM imports that expect it
        if (!importedModule.default) {
            importedModule = { default: importedModule, ...importedModule };
        }
        return importedModule;
    }
    /**
     * Create a SourceTextModule or SyntheticModule from source code
     */
    function createModuleFromSource(url, source, usePrivateGlobal) {
        // Handle JSON modules
        if (url.endsWith('.json')) {
            const jsonData = parseJsonModule(source, url);
            return new node_vm_1.SyntheticModule(['default'], function () {
                this.setExport('default', jsonData);
            }, { identifier: url, context });
        }
        // Strip TypeScript types if this is a .ts file
        if (url.endsWith('.ts') || url.endsWith('.tsx')) {
            source = stripTypeScriptTypes(source);
        }
        // Try CJS first since it will fail fast with clear syntax errors on ESM syntax
        try {
            return loadCJSModule(url, source, usePrivateGlobal);
        }
        catch (cjsErr) {
            // Only fallback to ESM if the error was due to ESM syntax (import/export statements)
            // Check if the error message indicates ESM syntax
            const errorMessage = cjsErr?.message || '';
            const isEsmSyntaxError = errorMessage.includes('import') ||
                errorMessage.includes('export') ||
                errorMessage.includes('Cannot use import statement') ||
                errorMessage.includes('Unexpected token');
            if (!isEsmSyntaxError) {
                // This was a real error (not ESM syntax), rethrow it
                throw cjsErr;
            }
            // If CJS loading fails due to ESM syntax, try ESM
            return new node_vm_1.SourceTextModule(source, {
                identifier: url,
                context,
                initializeImportMeta(meta) {
                    meta.url = url;
                },
                importModuleDynamically(specifier) {
                    const resolvedUrl = resolveModule(specifier, url);
                    const useApplicationLoader = shouldUseApplicationLoader(specifier, resolvedUrl);
                    return loadModuleWithCache(resolvedUrl, useApplicationLoader);
                },
            });
        }
    }
    /**
     * Create a module from URL without linking or evaluating (async version for initial load)
     */
    function createModule(url, usePrivateGlobal) {
        // Handle special built-in modules
        if (url === 'harper') {
            return createSyntheticModule(url, getHarperExports(scope));
        }
        if (url.startsWith('file://') && usePrivateGlobal) {
            checkAllowedModulePath(url, scope.allowedPath);
            const source = (0, node_fs_1.readFileSync)(new URL(url), { encoding: 'utf-8' });
            return createModuleFromSource(url, source, usePrivateGlobal);
        }
        // For Node.js built-in modules (node:) and npm packages without application loader for dependency
        const replacedModule = checkAllowedModulePath(url, scope.allowedPath);
        if (replacedModule) {
            return createSyntheticModule(url, normalizeImportedModule(replacedModule));
        }
        return import(__rewriteRelativeImportExtension(url)).then((importedModule) => createSyntheticModule(url, normalizeImportedModule(importedModule)));
    }
    // Load the entry module
    const entryModule = await loadModuleWithCache(moduleUrl, true);
    // Return the module namespace (exports)
    return entryModule.namespace;
}
async function getCompartment(scope, globals) {
    const { StaticModuleRecord } = await import('@endo/static-module-record');
    require('ses');
    const compartment = new Compartment(globals, {
    //harperdb: { Resource, tables, databases }
    }, {
        name: 'harper-app',
        resolveHook(moduleSpecifier, moduleReferrer) {
            if (HARPER_MODULE_IDS.has(moduleSpecifier)) {
                return 'harper'; // resolve any harper package as an alias to a single synthetic module
            }
            const parts = moduleSpecifier.split('/');
            if (parts[0] === 'harper') {
                // block harper/* for now (reserving for potential future use)
                throw new Error(`Module ${moduleSpecifier} is not allowed, may only access the 'harper' module`);
            }
            const resolved = (0, node_module_1.createRequire)(moduleReferrer).resolve(moduleSpecifier);
            if ((0, node_path_1.isAbsolute)(resolved)) {
                const resolvedURL = (0, node_url_1.pathToFileURL)(resolved).toString();
                return resolvedURL;
            }
            return moduleSpecifier;
        },
        importHook: async (moduleSpecifier) => {
            if (moduleSpecifier === 'harper') {
                const harperExports = getHarperExports(scope);
                return {
                    imports: [],
                    exports: Object.keys(harperExports),
                    execute(exports) {
                        Object.assign(exports, harperExports);
                    },
                };
            }
            else if (moduleSpecifier.startsWith('file:') && !moduleSpecifier.includes('node_modules')) {
                let moduleText = await (0, promises_1.readFile)(new URL(moduleSpecifier), { encoding: 'utf-8' });
                // Handle JSON files in compartment mode the same way as in VM mode
                if (moduleSpecifier.endsWith('.json')) {
                    const jsonData = parseJsonModule(moduleText, moduleSpecifier);
                    return {
                        imports: [],
                        exports: ['default'],
                        execute(exports) {
                            exports.default = jsonData;
                        },
                    };
                }
                // Strip TypeScript types if this is a .ts file
                if (moduleSpecifier.endsWith('.ts') || moduleSpecifier.endsWith('.tsx')) {
                    moduleText = stripTypeScriptTypes(moduleText);
                }
                return new StaticModuleRecord(moduleText, moduleSpecifier);
            }
            else {
                checkAllowedModulePath(moduleSpecifier, scope.allowedPath);
                const moduleExports = await import(__rewriteRelativeImportExtension(moduleSpecifier));
                return {
                    imports: [],
                    exports: Object.keys(moduleExports),
                    execute(exports) {
                        for (const key of Object.keys(moduleExports)) {
                            exports[key] = moduleExports[key];
                        }
                    },
                };
            }
        },
    });
    return compartment;
}
/**
 * This a constrained fetch. It certainly is not guaranteed to be safe, but requiring https may
 * be a good heuristic for preventing access to unsecured resources within a private network.
 * @param resource
 * @param options
 */
function secureOnlyFetch(resource, options) {
    // TODO: or maybe we should constrain by doing a DNS lookup and having disallow list of IP addresses that includes
    // this server
    const url = typeof resource === 'string' || resource.url;
    if (new URL(url).protocol != 'https')
        throw new Error('Only https is allowed in fetch');
    return fetch(resource, options);
}
// These globals need to match the literals produced in the VM context
const contextualizedJSGlobals = ['Object', 'Array', 'Function', 'globalThis'];
let defaultJSGlobalNames;
// get the global variable names that are intrinsically present in a VM context (so we don't override them)
function getDefaultJSGlobalNames() {
    if (!defaultJSGlobalNames) {
        defaultJSGlobalNames = (0, node_vm_1.runInContext)('Object.getOwnPropertyNames((function() { return this })())', (0, node_vm_1.createContext)({}));
    }
    return defaultJSGlobalNames;
}
/**
 * Get the set of global variables that should be available to modules that run in scoped compartments/contexts.
 */
function getGlobalObject(scope, copyIntrinsics = false) {
    const appGlobal = {};
    // create the new global object, assigning all the global variables from this global
    // except those that will be natural intrinsics of the new VM
    const globalsToExclude = copyIntrinsics ? contextualizedJSGlobals : getDefaultJSGlobalNames();
    for (let name of Object.getOwnPropertyNames(global)) {
        if (globalsToExclude.includes(name))
            continue;
        appGlobal[name] = global[name];
    }
    // now assign Harper scope-specific variables
    Object.assign(appGlobal, {
        server: scope.server ?? server,
        logger: scope.logger ?? harper_logger_js_1.default,
        resources: scope.resources,
        config: scope.config ?? {},
        fetch: APPLICATIONS_LOCKDOWN === 'ses' ? secureOnlyFetch : fetch,
        console,
        global: appGlobal,
        harper: getHarperExports(scope),
    });
    return appGlobal;
}
function getHarperExports(scope) {
    return {
        server: scope.server ?? server,
        logger: scope.logger ?? harper_logger_js_1.default,
        resources: scope.resources,
        config: scope.config ?? {},
        Resource: Resource_ts_1.Resource,
        tables: databases_ts_1.tables,
        databases: databases_ts_1.databases,
        createBlob,
        RequestTarget: RequestTarget_ts_1.RequestTarget,
        getContext,
        transaction: transaction_ts_1.transaction,
        getResponse,
        getUser,
        authenticateUser: server.authenticateUser,
        operation: server.operation,
        contentTypes: contentTypes_ts_1.contentTypes,
        Attribute: undefined,
        Config: undefined,
        ConfigValue: undefined,
        Context: undefined,
        FileAndURLPathConfig: undefined,
        FilesOption: undefined,
        FilesOptionObject: undefined,
        IterableEventQueue: undefined,
        Logger: undefined,
        Query: undefined,
        RecordObject: undefined,
        RequestTargetOrId: undefined,
        ResourceInterface: undefined,
        Scope: undefined,
        Session: undefined,
        SourceContext: undefined,
        SubscriptionRequest: undefined,
        Table: undefined,
        User: undefined,
    };
}
const ALLOWED_NODE_BUILTIN_MODULES = env.get(hdbTerms_ts_1.CONFIG_PARAMS.APPLICATIONS_ALLOWEDBUILTINMODULES)
    ? new Set(env.get(hdbTerms_ts_1.CONFIG_PARAMS.APPLICATIONS_ALLOWEDBUILTINMODULES))
    : {
        // if we don't have a list of allowed modules, allow everything
        has() {
            return true;
        },
    };
const ALLOWED_COMMANDS = new Set(env.get(hdbTerms_ts_1.CONFIG_PARAMS.APPLICATIONS_ALLOWEDSPAWNCOMMANDS) ?? []);
const child_processConstrained = {
    exec: createSpawn(child_process.exec),
    execFile: createSpawn(child_process.execFile),
    fork: createSpawn(child_process.fork, true), // this is launching node, so deemed safe
    spawn: createSpawn(child_process.spawn),
    execSync: function () {
        throw new Error('execSync is not allowed');
    },
};
child_processConstrained.default = child_processConstrained;
const REPLACED_BUILTIN_MODULES = {
    child_process: child_processConstrained,
};
/**
 * Creates a ChildProcess-like object for an existing process
 */
class ExistingProcessWrapper extends node_events_1.EventEmitter {
    pid;
    checkInterval;
    constructor(pid) {
        super();
        this.pid = pid;
        // Monitor process and emit exit event when it terminates
        this.checkInterval = setInterval(() => {
            try {
                // Signal 0 checks if process exists without actually killing it
                process.kill(pid, 0);
            }
            catch {
                // Process no longer exists
                clearInterval(this.checkInterval);
                this.emit('exit', null, null);
            }
        }, 1000);
    }
    // Kill the process
    kill(signal) {
        try {
            process.kill(this.pid, signal);
            return true;
        }
        catch {
            return false;
        }
    }
    // Clean up interval when wrapper is no longer needed
    unref() {
        clearInterval(this.checkInterval);
        return this;
    }
}
/**
 * Checks if a process with the given PID is running
 */
function isProcessRunning(pid) {
    try {
        // Signal 0 checks existence without killing
        process.kill(pid, 0);
        return true;
    }
    catch {
        return false;
    }
}
/**
 * Acquires an exclusive lock using the PID file itself (synchronously with busy-wait)
 * Returns 0 if lock was acquired (need to spawn new process), or the existing PID if process is running
 */
function parsePidFile(content) {
    const lines = content.trim().split('\n');
    const pid = Number.parseInt(lines[0], 10);
    const version = lines.length > 1 ? parseInt(lines[1], 10) : 0;
    return { pid, version };
}
function acquirePidFileLock(pidFilePath, requestedVersion, maxRetries = 100, retryDelay = 5) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {
            // Try to open exclusively - 'wx' fails if file exists
            const fd = (0, node_fs_1.openSync)(pidFilePath, 'wx');
            (0, node_fs_1.closeSync)(fd);
            return { pid: 0, version: 0 }; // Successfully acquired lock (file created), caller should spawn process
        }
        catch (err) {
            if (err.code === 'EEXIST') {
                // File exists - check if it contains a valid running process
                try {
                    const pidContent = (0, node_fs_1.readFileSync)(pidFilePath, 'utf-8');
                    const { pid: existingPid, version: existingVersion } = parsePidFile(pidContent);
                    if (!isNaN(existingPid) && isProcessRunning(existingPid)) {
                        // If the version isn't the one we want, kill the existing process and re-acquire
                        if (requestedVersion != null && requestedVersion !== existingVersion) {
                            try {
                                process.kill(existingPid);
                            }
                            catch {
                                // Process may have already exited
                            }
                            try {
                                (0, node_fs_1.unlinkSync)(pidFilePath);
                            }
                            catch {
                                // Another thread may have removed it
                            }
                            // Retry to acquire the lock for the new version
                            const start = Date.now();
                            while (Date.now() - start < retryDelay) {
                                // Busy wait for process cleanup
                            }
                            continue;
                        }
                        // Valid process is running at same or higher version, return its PID
                        return { pid: existingPid, version: existingVersion };
                    }
                    // Invalid/empty PID - check file age to determine if it's stale or being written
                    const stats = (0, node_fs_1.statSync)(pidFilePath);
                    const fileAge = Date.now() - stats.mtimeMs;
                    // If file is very new (less than 100ms) and empty/invalid, another thread is likely still writing to it
                    if (fileAge < 100) {
                        // Just wait and retry, don't try to remove
                    }
                    else {
                        // Stale PID file (old and invalid), try to remove it
                        try {
                            (0, node_fs_1.unlinkSync)(pidFilePath);
                        }
                        catch {
                            // Another thread may have removed it, retry
                        }
                    }
                }
                catch {
                    // Couldn't read/stat file, another thread might be modifying it, retry
                }
                // Wait a bit before retrying
                const start = Date.now();
                while (Date.now() - start < retryDelay) {
                    // Busy wait
                }
            }
            else {
                throw err;
            }
        }
    }
    throw new Error(`Failed to acquire PID file lock after ${maxRetries} attempts`);
}
function createSpawn(spawnFunction, alwaysAllow) {
    const basePath = env.getHdbBasePath();
    return function (command, args, options, callback) {
        if (!ALLOWED_COMMANDS.has(command.split(' ')[0]) && !alwaysAllow) {
            throw new Error(`Command ${command} is not allowed`);
        }
        const processName = options?.name;
        if (!processName)
            throw new Error(`Calling ${spawnFunction.name} in Harper must have a process "name" in the options to ensure that a single process is started and reused`);
        const requestedVersion = options?.version;
        // Ensure PID directory exists
        const pidDir = (0, node_path_2.join)(basePath, 'pids');
        (0, node_fs_1.mkdirSync)(pidDir, { recursive: true });
        const pidFilePath = (0, node_path_2.join)(pidDir, `${processName}.pid`);
        // Try to acquire lock - returns pid: 0 if acquired, or existing PID/version
        const existing = acquirePidFileLock(pidFilePath, requestedVersion);
        if (existing.pid !== 0) {
            // Existing process is running, return wrapper
            return new ExistingProcessWrapper(existing.pid);
        }
        // We acquired the lock (file was created), spawn new process
        const childProcess = spawnFunction(command, args, options, callback);
        // Write PID (and version if provided) to the file we just created
        const pidFileContent = requestedVersion != null ? `${childProcess.pid}\n${requestedVersion}` : childProcess.pid.toString();
        try {
            (0, node_fs_1.writeFileSync)(pidFilePath, pidFileContent, 'utf-8');
        }
        catch (err) {
            // Failed to write PID, clean up
            try {
                childProcess.kill();
                (0, node_fs_1.unlinkSync)(pidFilePath);
            }
            catch { }
            throw err;
        }
        // Clean up PID file when process exits
        childProcess.on('exit', () => {
            try {
                (0, node_fs_1.unlinkSync)(pidFilePath);
            }
            catch {
                // File may already be removed
            }
        });
        return childProcess;
    };
}
/**
 * Validates whether a module can be loaded based on security restrictions and returns the module path or replacement.
 * For file URLs, ensures the module is within the allowed path.
 * For node built-in modules, checks against an allowlist and returns any replacements.
 *
 * @param {string} moduleUrl - The URL or identifier of the module to be loaded, which may be a file: URL, node: URL, or bare module specifier.
 * @param {string} allowedPath - The absolute path that the module is allowed to load from.
 * @return {any} Returns undefined for allowed file paths, or a replacement module identifier for allowed node built-in modules.
 * @throws {Error} Throws an error if the module is outside the allowed path or if the module is not in the allowed list.
 */
function checkAllowedModulePath(moduleUrl, allowedPath) {
    if (moduleUrl.startsWith('file:')) {
        let path = moduleUrl.slice(7);
        try {
            path = (0, node_fs_1.realpathSync)(path);
        }
        catch { }
        if (!allowedPath || path.startsWith(allowedPath)) {
            return;
        }
        throw new Error(`Can not load module outside of allowed path`);
    }
    let simpleName = moduleUrl.startsWith('node:') ? moduleUrl.slice(5) : moduleUrl;
    simpleName = simpleName.split('/')[0];
    if (ALLOWED_NODE_BUILTIN_MODULES.has(simpleName))
        return REPLACED_BUILTIN_MODULES[simpleName];
    throw new Error(`Module ${moduleUrl} is not allowed to be imported`);
}
function getContext() {
    return transaction_ts_1.contextStorage.getStore() ?? {};
}
function getUser() {
    return transaction_ts_1.contextStorage.getStore()?.user;
}
function getResponse() {
    return transaction_ts_1.contextStorage.getStore()?.response;
}
function preventFunctionConstructor() {
    Function.prototype.constructor = function () { }; // prevent this from being used to eval data in a parent context
}
function freezeIntrinsics() {
    overridableProperty(Object.prototype, 'toString');
    overridableProperty(Object.prototype, 'valueOf');
    overridableProperty(Object.prototype, 'hasOwnProperty');
    overridableProperty(Object.prototype, 'constructor');
    overridableProperty(Promise.prototype, 'then');
    overridableProperty(Date, 'now');
    for (let name of ['get', 'set', 'has', 'delete', 'clear', 'forEach', 'entries', 'keys', 'values']) {
        overridableProperty(Map.prototype, name);
    }
    for (let Intrinsic of [
        Object,
        Array,
        Promise,
        BigInt,
        String,
        Number,
        Boolean,
        Symbol,
        RegExp,
        Date,
        Map,
        Set,
        WeakMap,
        WeakSet,
        Math,
        JSON,
        Reflect,
        Atomics,
        SharedArrayBuffer,
        WeakRef,
        FinalizationRegistry,
    ]) {
        Object.freeze(Intrinsic);
        Object.freeze(Intrinsic.prototype);
    }
    Object.freeze(Function);
}
/**
 * This can redefine a property into a getter/setter that will allow derivatives of a prototype to assign
 * a value to the property without incurring an error from the property being frozen and readonly.
 * @param target
 * @param name
 * @param value
 */
function overridableProperty(target, name, value = target[name]) {
    Object.defineProperty(target, name, {
        get() {
            return value;
        },
        set(value) {
            Object.defineProperty(this, name, {
                value,
                configurable: true,
                enumerable: true,
                writable: true,
            });
        },
    });
}
//# sourceMappingURL=jsLoader.js.map