Files
ior-resolver/docs/IOR_RESOLVER.md
2025-08-29 05:08:16 +02:00

17 KiB

IOR Core Resolver - Implementation Guide

Overview

The IOR Core Resolver provides a unified architecture for resolving Interoperable Object References (IOR) across different JavaScript environments. This document serves as a technical guide for implementing IOR resolution in bundlers and loaders beyond the existing Metro and Node.js implementations.

Core Architecture

The IOR resolver architecture consists of three main layers:

1. Shared Core (ior-core.js)

The foundation that provides:

  • IOR string parsing and validation
  • Remote component fetching (GitHub, Gitea, IPFS)
  • Local component discovery
  • Caching mechanisms
  • Archive extraction and entry point resolution

2. Environment-Specific Implementations

  • Metro Resolver (metro-ior-resolver.js) - React Native bundler integration
  • Node Import Loader (NodeImportLoader.ts) - Native Node.js module loading
  • Future Implementations - Webpack, Vite, Rollup, etc.

3. Supporting Tools

  • Pre-build Type Generator (prebuild-ior-types.js) - TypeScript declaration generation
  • Remote Component Fetcher (fetch-remote-component.js) - Standalone fetching utility

Implementation Requirements

When implementing IOR resolution for a new bundler or loader, ensure the following requirements are met:

1. Module Resolution Hook

Your bundler must provide a way to intercept module resolution requests:

// Example pattern
function resolveRequest(context, moduleName, platform) {
  if (moduleName.startsWith('ior:')) {
    return resolveIORModule(moduleName);
  }
  return defaultResolve(moduleName);
}

2. Synchronous or Asynchronous Support

Depending on your bundler's architecture:

  • Synchronous (like Metro): Use sync-fetch or pre-cached components
  • Asynchronous (like Node.js loader): Use native fetch or https modules

3. File System Access

Required for:

  • Scanning local components
  • Managing cache directories
  • Extracting downloaded archives

Core Functions Reference

The ior-core.js module exposes these essential functions for implementing IOR resolution:

Parsing & Validation

parseRemoteIOR(ior)           // Parse remote IOR strings
parsePackageJson(path)         // Sync parse package.json
parsePackageJsonAsync(path)    // Async parse package.json

Remote Fetching

fetchUrl(url)                  // Fetch text content
fetchUrlBuffer(url)            // Fetch as Buffer
fetchFromGitHub(config)        // GitHub-specific fetching
fetchFromGitea(config)         // Gitea-specific fetching
fetchFromIPFS(config)          // IPFS gateway fetching

Local Resolution

buildLocalIORMappings(root)    // Scan and map local components
findEntryPoint(dir, name)      // Locate component entry file

Caching & Extraction

generateCacheHash(ior)         // SHA256 hash for cache keys
extractArchive(buffer, dir)    // Extract tar.gz archives

Implementation Examples

Metro Resolver Pattern

const { buildLocalIORMappings, fetchRemoteComponent } = require('@metatrom/ior-resolver/core');

function createResolver(projectRoot) {
  const mappings = buildLocalIORMappings(projectRoot);
  
  return (context, moduleName, platform) => {
    if (moduleName.startsWith('ior:')) {
      // Check local mappings first
      if (mappings[moduleName]) {
        return { filePath: mappings[moduleName] };
      }
      
      // Fetch remote component synchronously
      const cachedPath = fetchRemoteComponentSync(moduleName);
      return { filePath: cachedPath };
    }
    // Default resolution
    return context.resolveRequest(context, moduleName, platform);
  };
}

Node.js Loader Pattern

import { parseRemoteIOR, fetchFromGitHub } from '@metatrom/ior-resolver/core';

export async function resolve(specifier, context, defaultResolve) {
  if (specifier.startsWith('ior:')) {
    const config = parseRemoteIOR(specifier);
    if (config) {
      const localPath = await fetchAndCache(config);
      return { url: pathToFileURL(localPath).href };
    }
  }
  return defaultResolve(specifier, context, defaultResolve);
}

IOR Format Specification

Format Structure

The IOR format follows a consistent pattern across all sources:

ior:[protocol]:[location]:[component]@[version]

Protocol Types

| Protocol | Description | Location Format | Example | |----------|-------------|-----------------|---------|| | esm | Local ESM modules | Component namespace | ior:esm:com.example.logger@1.0.0 | | github | GitHub repositories | owner/repo | ior:github:facebook/react:scheduler@0.23.0 | | gitea | Gitea instances | instance:owner/repo | ior:gitea:git.company.com:team/auth@2.0.0 | | ipfs | IPFS network | Content hash | ior:ipfs:QmHash:component@1.0.0 | | p2p | P2P network (planned) | Peer ID | ior:p2p:peer-id:component@1.0.0 |

Version Resolution

  • Git-based (GitHub/Gitea): Uses Git tags (1.0.0 or v1.0.0)
  • Local: Matches package.json version field
  • IPFS: Version included in path structure
  • P2P: To be determined in future implementation

Resolution Algorithm

Implement IOR resolution following this algorithm:

function resolveIOR(ior, options = {}) {
  // Step 1: Validate IOR format
  if (!ior.startsWith('ior:')) {
    return null;
  }
  
  // Step 2: Check if local component
  if (ior.includes(':esm:')) {
    return resolveLocalComponent(ior, options);
  }
  
  // Step 3: Parse remote IOR
  const config = parseRemoteIOR(ior);
  if (!config) {
    throw new Error(`Invalid IOR format: ${ior}`);
  }
  
  // Step 4: Check cache
  const cachedPath = checkCache(config, options.cacheTTL);
  if (cachedPath) {
    return cachedPath;
  }
  
  // Step 5: Fetch and cache
  const fetchedPath = fetchAndCache(config, options);
  return fetchedPath;
}

Resolution Steps

  1. Format Validation: Ensure IOR follows expected pattern
  2. Local vs Remote: Determine resolution strategy
  3. Cache Check: Look for valid cached version
  4. Remote Fetch: Download if not cached
  5. Entry Point: Locate main file in component
  6. Path Return: Provide absolute path to bundler

Caching Implementation

Cache Strategy

Implement a two-tier caching system:

  1. Memory Cache (Optional)

    • Short-lived resolution cache
    • Avoids repeated file system checks
    • Clear on file changes
  2. File System Cache (Required)

    • Persistent component storage
    • TTL-based invalidation
    • Archive extraction location

Cache Structure

.ior-cache/
├── remote/
│   ├── {sha256-hash}/          # Extracted component
│   │   ├── package.json
│   │   ├── index.ts
│   │   └── component.d.ts
│   └── metadata.json            # Cache metadata
└── mappings.json                # IOR to path mappings

Cache Metadata

{
  "ior": "ior:github:owner/repo:component@1.0.0",
  "cachedAt": "2025-08-28T10:00:00Z",
  "ttl": 3600000,
  "entryPoint": "index.ts",
  "hash": "sha256-hash"
}

Cache Location Considerations

| Environment | Recommended Location | Reason | |-------------|---------------------|---------|| | Metro | Project .ior-cache/ | Watchman compatibility | | Node.js | OS temp directory | System conventions | | Webpack | node_modules/.cache/ | Standard practice | | Vite | node_modules/.vite/ | Framework convention |

Bundler Integration Examples

Webpack Plugin (Conceptual)

class IORResolverPlugin {
  constructor(options = {}) {
    this.cacheDir = options.cacheDir || '.ior-cache';
    this.ttl = options.ttl || 3600000;
  }
  
  apply(compiler) {
    compiler.hooks.resolve.tapAsync('IORResolver', (request, resolveContext, callback) => {
      if (request.request?.startsWith('ior:')) {
        const resolved = this.resolveIOR(request.request);
        if (resolved) {
          request.path = resolved;
          return callback();
        }
      }
      callback();
    });
  }
}

Vite Plugin (Conceptual)

export function iorResolver(options = {}) {
  return {
    name: 'vite-plugin-ior',
    async resolveId(source) {
      if (source.startsWith('ior:')) {
        const resolved = await resolveIOR(source, options);
        return resolved;
      }
      return null;
    }
  };
}

Testing Your Implementation

Unit Tests

// Test IOR parsing
test('parseRemoteIOR', () => {
  const config = parseRemoteIOR('ior:github:owner/repo:component@1.0.0');
  expect(config).toEqual({
    protocol: 'github',
    owner: 'owner',
    repo: 'repo',
    component: 'component',
    version: '1.0.0'
  });
});

// Test cache functionality
test('caching', async () => {
  const ior = 'ior:github:test/repo:component@1.0.0';
  const path1 = await resolveIOR(ior);
  const path2 = await resolveIOR(ior);
  expect(path1).toBe(path2); // Should use cache
});

Integration Tests

// Test with actual bundler
test('bundler integration', async () => {
  const bundle = await bundler.build({
    entry: './test-ior-imports.js',
    plugins: [iorResolverPlugin()]
  });
  expect(bundle.modules).toContain('ior:github:owner/repo:component@1.0.0');
});

Performance Optimization

Bundler-Specific Optimizations

  1. Pre-fetching Strategy

    • Scan codebase for IOR imports before bundling
    • Fetch all remote components in parallel
    • Generate type declarations upfront
  2. Async vs Sync Resolution

    • Use async when bundler supports it (Webpack, Vite)
    • Implement sync with pre-cached components (Metro)
    • Consider worker threads for CPU-intensive operations
  3. Cache Warming

    // Pre-warm cache during CI/CD
    const iors = scanForIORImports('./src');
    await Promise.all(iors.map(fetchRemoteComponent));
    
  4. Memory Optimization

    • Implement LRU cache for memory mappings
    • Stream large archives during extraction
    • Clean expired cache entries periodically

Type Generation Strategy

Automatic Type Generation

Implement type generation following this approach:

  1. Scan Phase

    const iorImports = scanSourceFiles({
      root: './src',
      extensions: ['.ts', '.tsx', '.js', '.jsx'],
      pattern: /from\s+['"]ior:[^'"]+['"]/g
    });
    
  2. Fetch & Extract Types

    for (const ior of iorImports) {
      const cachePath = await fetchComponent(ior);
      const types = extractTypeDefinitions(cachePath);
      typeMap[ior] = types;
    }
    
  3. Generate Declaration File

    // auto-generated.d.ts
    declare module 'ior:github:owner/repo:component@1.0.0' {
      export interface ComponentProps { /* extracted */ }
      export const Component: React.FC<ComponentProps>;
    }
    

Implementation Checklist

When implementing IOR resolution for a new bundler:

Required Features

  • IOR format parsing
  • Local component resolution
  • Remote component fetching (at least one protocol)
  • File system caching with TTL
  • Entry point resolution
  • Error handling and logging
  • TypeScript declaration generation
  • Pre-build scanning and fetching
  • Hot reload support for local components
  • Cache invalidation mechanisms
  • Parallel fetching for multiple components
  • Progress reporting for large downloads

Optional Enhancements

  • Memory caching layer
  • Custom protocol support
  • Private repository authentication
  • Cache compression
  • Dependency graph analysis
  • Build-time optimization

Component Publishing Guidelines

Repository Structure

Components should follow this structure for optimal compatibility:

component-repo/
├── package.json         # Component metadata
├── index.ts            # Main entry (or specified in package.json)
├── index.d.ts          # TypeScript declarations
├── README.md           # Documentation
└── src/                # Additional source files (optional)
    └── ...

Metadata Requirements

Minimal package.json

{
  "name": "component-name",
  "version": "1.0.0",
  "main": "index.ts",
  "types": "index.d.ts"
}

Full Metatrom Integration

{
  "name": "@org/component",
  "version": "1.0.0",
  "main": "index.ts",
  "types": "index.d.ts",
  "type": "module",
  "metatrom": {
    "ior": "com.example.component@1.0.0",
    "protocols": ["github", "gitea", "ipfs"],
    "capabilities": {
      "platform": ["react-native", "web", "node"],
      "features": ["typescript", "async"]
    }
  }
}

Versioning Best Practices

  1. Use semantic versioning (MAJOR.MINOR.PATCH)
  2. Tag releases in Git: git tag v1.0.0 or git tag 1.0.0
  3. Include CHANGELOG.md for version history
  4. Ensure backward compatibility within minor versions

Error Handling Guidelines

Error Categories

  1. Resolution Errors

    class IORResolutionError extends Error {
      constructor(ior, reason) {
        super(`Failed to resolve ${ior}: ${reason}`);
        this.name = 'IORResolutionError';
        this.ior = ior;
      }
    }
    
  2. Network Errors

    • Retry with exponential backoff
    • Fall back to alternative gateways (IPFS)
    • Provide clear error messages with recovery steps
  3. Cache Errors

    • Auto-clear corrupted cache entries
    • Fall back to re-fetching
    • Log cache operations for debugging

Error Recovery Strategies

async function resolveWithRetry(ior, maxRetries = 3) {
  let lastError;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await resolveIOR(ior);
    } catch (error) {
      lastError = error;
      
      // Clear cache if corrupted
      if (error.code === 'CACHE_CORRUPTED') {
        clearCache(ior);
      }
      
      // Wait before retry
      await delay(Math.pow(2, i) * 1000);
    }
  }
  
  throw new IORResolutionError(ior, lastError.message);
}

Security Considerations

Component Validation

  1. Source Verification

    • Validate HTTPS certificates for Git sources
    • Verify IPFS content hashes
    • Check component signatures (future)
  2. Content Sanitization

    • Scan for malicious patterns
    • Validate package.json structure
    • Restrict file system access
  3. Sandboxing Options

    const secureResolver = createIORResolver({
      allowedProtocols: ['github', 'gitea'],
      allowedDomains: ['github.com', 'git.company.com'],
      maxComponentSize: 10 * 1024 * 1024, // 10MB
      scanForMalware: true
    });
    

Authentication Support

For private repositories:

const resolver = createIORResolver({
  auth: {
    github: process.env.GITHUB_TOKEN,
    gitea: {
      'git.company.com': process.env.GITEA_TOKEN
    }
  }
});

Platform-Specific Considerations

React Native (Metro)

  • Synchronous resolution required
  • Watchman integration for file watching
  • SHA-1 hashing for module identification
  • Bundle-time resolution only

Node.js

  • Async resolution preferred
  • Native ES modules support
  • TypeScript transpilation needed
  • Runtime resolution possible

Web Bundlers (Webpack/Vite)

  • Plugin architecture varies
  • Build-time optimization opportunities
  • Tree-shaking considerations
  • Source map generation

Cross-Platform Compatibility

Ensure your implementation handles:

  • Path separators (/ vs \)
  • Case sensitivity differences
  • Symlink support variations
  • File system permissions

Future Roadmap

Short Term (v2.0)

  • Private repository authentication
  • Improved error messages and recovery
  • Performance metrics and monitoring
  • Plugin templates for popular bundlers

Medium Term (v3.0)

  • P2P protocol implementation (libp2p)
  • Component dependency resolution
  • Versioning conflict resolution
  • Cross-platform component compatibility checks

Long Term

  • Decentralized component registry
  • Smart contract integration for Web3
  • Component marketplace
  • AI-assisted component discovery

Contributing

To add support for a new bundler:

  1. Study the ior-core.js implementation
  2. Create bundler-specific adapter using core functions
  3. Implement required resolver hooks
  4. Add comprehensive tests
  5. Document integration steps
  6. Submit PR with examples

Resources

Conclusion

The IOR Core Resolver architecture provides a robust foundation for implementing component resolution across any JavaScript bundler or loader. By following this guide and leveraging the shared core functions, you can enable IOR support in your build system while maintaining consistency with existing implementations.