UNPKG

mlld

Version:

mlld: a modular prompt scripting language

319 lines (317 loc) 12 kB
import { satisfiesVersion, parseSemVer, compareSemVer } from './chunk-LTNARAHB.mjs'; import { TaintLevel } from './chunk-KYJC7SAY.mjs'; import { logger } from './chunk-XGMRAGIT.mjs'; import { MlldResolutionError } from './chunk-YMCO2JI3.mjs'; import { __name, __publicField } from './chunk-OMKLS24H.mjs'; // core/resolvers/RegistryResolver.ts var _RegistryResolver = class _RegistryResolver { constructor() { __publicField(this, "name", "REGISTRY"); __publicField(this, "description", "Resolves public modules using GitHub registry at mlld-lang/registry"); __publicField(this, "type", "input"); __publicField(this, "capabilities", { io: { read: true, write: false, list: false }, contexts: { import: true, path: false, output: false }, supportedContentTypes: [ "module" ], defaultContentType: "module", priority: 10, cache: { strategy: "persistent", ttl: { duration: 300 } // 5 minutes } }); __publicField(this, "cache", /* @__PURE__ */ new Map()); __publicField(this, "defaultCacheTimeout", 3e5); __publicField(this, "defaultRegistryRepo", "mlld-lang/registry"); __publicField(this, "defaultBranch", "main"); } /** * Parse module reference with version support */ parseModuleReference(ref) { const match = ref.match(/^@([^/]+)\/([^@]+)(?:@(.+))?$/); if (!match) { throw new MlldResolutionError(`Invalid module reference format. Expected @user/module or @user/module@version, got: ${ref}`); } const [, author, module, version] = match; const isTag = version && !/^[\d^~<>=]/.test(version); return { author, module, version, isTag }; } /** * Check if this resolver can handle the reference * Registry resolver handles @user/module pattern with optional version */ canResolve(ref, config) { if (!ref.startsWith("@")) return false; try { this.parseModuleReference(ref); return true; } catch { return false; } } /** * Resolve version for a module */ resolveVersion(available, requested) { const sorted = available.filter((v) => satisfiesVersion(v, requested)).sort((a, b) => { const va = parseSemVer(a); const vb = parseSemVer(b); return compareSemVer(vb, va); }); return sorted[0] || null; } /** * Fetch version-specific data from API or GitHub */ async fetchVersionData(author, module, version, registryRepo, branch, config) { const versionUrl = `https://raw.githubusercontent.com/${registryRepo}/${branch}/modules/${author}/${module}/${version}.json`; const headers = { "Accept": "application/json", "User-Agent": "mlld-registry-resolver" }; if (config?.token) { headers["Authorization"] = `token ${config.token}`; } const response = await fetch(versionUrl, { headers }); if (!response.ok) { throw new MlldResolutionError(`Failed to fetch version data for @${author}/${module}@${version}: ${response.status}`); } return response.json(); } /** * Resolve a module reference using GitHub registry */ async resolve(ref, config) { const { author, module, version, isTag } = this.parseModuleReference(ref); const moduleKey = `@${author}/${module}`; const registryConfig = { registryRepo: config?.registryRepo, branch: config?.branch, cacheTimeout: config?.cacheTimeout, token: config?.token }; const registryRepo = registryConfig.registryRepo || this.defaultRegistryRepo; const branch = registryConfig.branch || this.defaultBranch; logger.debug(`Resolving ${ref} from registry: ${registryRepo}`); try { const registryFile = await this.fetchRegistry(registryRepo, branch, registryConfig); const moduleEntry = registryFile.modules[moduleKey]; if (!moduleEntry) { throw new MlldResolutionError(`Module '${module}' not found in ${author}'s registry`); } let resolvedVersion = moduleEntry.version; let versionData = moduleEntry; if (version) { if (isTag && moduleEntry.tags?.[version]) { resolvedVersion = moduleEntry.tags[version]; logger.debug(`Resolved tag '${version}' to version ${resolvedVersion}`); } else if (moduleEntry.availableVersions) { const resolved = this.resolveVersion(moduleEntry.availableVersions, version || "latest"); if (!resolved) { throw new MlldResolutionError(`No version matching '${version}' for ${moduleKey} Available versions: ${moduleEntry.availableVersions.join(", ")}`); } resolvedVersion = resolved; } else if (version !== moduleEntry.version) { throw new MlldResolutionError(`Version ${version} not found for ${moduleKey}. Only version ${moduleEntry.version} is available.`); } } if (resolvedVersion !== moduleEntry.version && moduleEntry.availableVersions) { logger.debug(`Fetching version data for ${moduleKey}@${resolvedVersion}`); versionData = await this.fetchVersionData(author, module, resolvedVersion, registryRepo, branch, registryConfig); } const sourceUrl = versionData.source.url; logger.debug(`Resolved ${ref} to ${moduleKey}@${resolvedVersion} at ${sourceUrl}`); logger.debug(`Fetching module content from: ${sourceUrl}`); const response = await fetch(sourceUrl); if (!response.ok) { if (response.status === 404) { throw new MlldResolutionError(`Module content not found at ${sourceUrl}. The module may have been moved or deleted.`); } throw new MlldResolutionError(`Failed to fetch module content from ${sourceUrl}: ${response.status} ${response.statusText}`); } const content = await response.text(); if (!content || content.length === 0) { throw new MlldResolutionError(`Module content is empty at ${sourceUrl}`); } if (process.env.MLLD_DEBUG === "true") { console.log(`[RegistryResolver] Resolved to version: ${resolvedVersion}`); console.log(`[RegistryResolver] Fetched content from ${sourceUrl}`); console.log(`[RegistryResolver] Fetched content length: ${content.length}`); console.log(`[RegistryResolver] First 200 chars:`, content.substring(0, 200)); } return { content, contentType: "module", metadata: { source: `registry://${moduleKey}@${resolvedVersion}`, timestamp: /* @__PURE__ */ new Date(), taintLevel: TaintLevel.PUBLIC, author: moduleEntry.author, mimeType: "text/x-mlld-module", hash: versionData.source.contentHash, sourceUrl, version: resolvedVersion } }; } catch (error) { if (error instanceof MlldResolutionError) { throw error; } const errorMessage = error instanceof Error ? error.message : String(error); throw new MlldResolutionError(`Failed to resolve ${ref} from registry: ${errorMessage}`); } } /** * Validate configuration */ validateConfig(config) { const errors = []; if (config?.registryRepo !== void 0 && typeof config.registryRepo !== "string") { errors.push("registryRepo must be a string"); } if (config?.branch !== void 0 && typeof config.branch !== "string") { errors.push("branch must be a string"); } if (config?.cacheTimeout !== void 0) { if (typeof config.cacheTimeout !== "number" || config.cacheTimeout < 0) { errors.push("cacheTimeout must be a non-negative number"); } } if (config?.token !== void 0 && typeof config.token !== "string") { errors.push("token must be a string"); } return errors; } /** * Check access - registry modules are always public/readable */ async checkAccess(ref, operation, config) { if (operation === "write") { return false; } return this.canResolve(ref, config); } /** * Fetch the centralized registry file from GitHub */ async fetchRegistry(registryRepo, branch, config) { const cacheKey = `${registryRepo}:${branch}:modules`; const cached = this.getCachedRegistry(cacheKey, config?.cacheTimeout); if (cached) { logger.debug(`Registry cache hit`); return cached; } const registryUrl = `https://raw.githubusercontent.com/${registryRepo}/${branch}/modules.json`; logger.debug(`Fetching registry from: ${registryUrl}`); const headers = { "Accept": "application/json", "User-Agent": "mlld-registry-resolver" }; if (config?.token) { headers["Authorization"] = `token ${config.token}`; } const response = await fetch(registryUrl, { headers }); if (!response.ok) { if (response.status === 404) { throw new MlldResolutionError(`Registry not found at ${registryUrl}. The registry may be unavailable.`, { registryUrl }); } throw new Error(`GitHub API error: ${response.status} ${response.statusText}`); } const registryData = await response.json(); this.validateRegistryFile(registryData); this.cache.set(cacheKey, { content: registryData, timestamp: Date.now() }); logger.debug(`Cached registry with ${Object.keys(registryData.modules || {}).length} modules`); return registryData; } /** * Get cached registry if available and not expired */ getCachedRegistry(cacheKey, timeout) { const cached = this.cache.get(cacheKey); if (!cached) return null; const maxAge = timeout || this.defaultCacheTimeout; if (Date.now() - cached.timestamp > maxAge) { this.cache.delete(cacheKey); return null; } return cached.content; } /** * Validate registry file format */ validateRegistryFile(data) { if (!data || typeof data !== "object") { throw new Error("Registry file must be a valid JSON object"); } if (!data.version) { throw new Error("Registry file missing version field"); } if (!data.modules || typeof data.modules !== "object") { throw new Error("Registry file missing or invalid modules field"); } for (const [moduleName, moduleData] of Object.entries(data.modules)) { if (!moduleData || typeof moduleData !== "object") { throw new Error(`Invalid module entry for '${moduleName}'`); } const module = moduleData; if (!module.name || typeof module.name !== "string") { throw new Error(`Module '${moduleName}' missing or invalid name field`); } if (!module.author || typeof module.author !== "string") { throw new Error(`Module '${moduleName}' missing or invalid author field`); } if (!module.about || typeof module.about !== "string") { throw new Error(`Module '${moduleName}' missing or invalid about field`); } if (!module.source || typeof module.source !== "object") { throw new Error(`Module '${moduleName}' missing or invalid source field`); } if (!module.source.url || typeof module.source.url !== "string") { throw new Error(`Module '${moduleName}' missing or invalid source.url field`); } if (!module.source.contentHash || typeof module.source.contentHash !== "string") { throw new Error(`Module '${moduleName}' missing or invalid source.contentHash field`); } if (!Array.isArray(module.needs)) { throw new Error(`Module '${moduleName}' missing or invalid needs field (must be array)`); } if (module.license !== "CC0") { throw new Error(`Module '${moduleName}' must have CC0 license`); } } } }; __name(_RegistryResolver, "RegistryResolver"); var RegistryResolver = _RegistryResolver; export { RegistryResolver }; //# sourceMappingURL=chunk-2U4LJYI7.mjs.map //# sourceMappingURL=chunk-2U4LJYI7.mjs.map