mlld
Version:
mlld: a modular prompt scripting language
319 lines (317 loc) • 12 kB
JavaScript
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