Module: Capsium::Package::Composition

Included in:
Capsium::Package
Defined in:
lib/capsium/package/composition.rb,
sig/capsium/package/composition.rbs

Overview

Composite-package support (ARCHITECTURE.md section 4a), mixed into Package: metadata.dependencies resolution — bundled dependencies (Capsium::Package::Bundle) first, then the package store — and load-time validation of dependency resource references.

Instance Method Summary collapse

Instance Method Details

#dependency_for(reference) ⇒ ResolvedDependency?

Parameters:

  • reference (String)

Returns:



80
81
82
83
84
# File 'lib/capsium/package/composition.rb', line 80

def dependency_for(reference)
  @resolved_dependencies
    .sort_by { |dependency| -dependency.guid.length }
    .find { |dependency| reference.start_with?("#{dependency.guid}/") }
end

#dependency_viewsArray[[String, MergedView]]

Returns:



86
87
88
89
90
# File 'lib/capsium/package/composition.rb', line 86

def dependency_views
  @resolved_dependencies.map do |dependency|
    [dependency.guid, dependency.package.merged_view(exported_only: true)]
  end
end

#resolve_dependencies(store, registry, chain, bundle = nil) ⇒ Array[ResolvedDependency]

Resolves metadata.dependencies: bundled dependencies first (Capsium::Package::Bundle — encapsulated packages embed their dependencies under packages/, so they activate with no store or registry), then the store (the store argument, else CAPSIUM_STORE) with the given registry (else CAPSIUM_REGISTRY) as install fallback for store misses. The store resolver is only constructed when a dependency is not bundled, so a fully bundled package needs neither. Each resolved dependency loads recursively with an extended circularity chain and this package's bundle underneath its own (re-declared transitive dependencies resolve from it); its exported content becomes lower read-only layers of this package's merged view.

Parameters:

Returns:



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/capsium/package/composition.rb', line 24

def resolve_dependencies(store, registry, chain, bundle = nil)
  declared = @metadata.dependencies
  return [] if declared.empty?

  chain += [@metadata.guid]
  bundle = Bundle.new(@path).merged_with(bundle)
  resolver = nil
  declared.map do |guid, range|
    cap_path = bundle.resolve_path(guid, range, chain: chain)
    if cap_path.nil?
      resolver ||= DependencyResolver.new(store || Store.default, registry: registry)
      cap_path = resolver.resolve_path(guid, range, chain: chain)
    end
    ResolvedDependency.new(
      guid: guid, range: range, path: cap_path,
      package: Package.new(cap_path,
                           store: resolver ? resolver.store : store,
                           registry: resolver ? resolver.registry : registry,
                           dependency_chain: chain,
                           bundle: bundle)
    )
  end
end

#validate_dependency_reference(route, dependency, reference) ⇒ void

This method returns an undefined value.

Parameters:

Raises:



66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/capsium/package/composition.rb', line 66

def validate_dependency_reference(route, dependency, reference)
  inner = reference.delete_prefix("#{dependency.guid}/")
  return if dependency.package.merged_view(exported_only: true).resolve(inner)

  if dependency.package.merged_view.resolve(inner)
    raise DependencyVisibilityError,
          "route #{route.path} references a private resource of " \
          "dependency #{dependency.guid}: #{inner}"
  end
  raise DependencyError,
        "route #{route.path} references a resource missing from " \
        "dependency #{dependency.guid}: #{inner}"
end

#validate_dependency_references!void

This method returns an undefined value.

Every route resource given as a URI ("/") must address a resolved dependency and a resource that dependency exports; anything else is a load-time error (section 4a).



51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/capsium/package/composition.rb', line 51

def validate_dependency_references!
  @routes.config.routes.each do |route|
    reference = route.resource
    next unless route.dependency_reference?

    dependency = dependency_for(reference)
    unless dependency
      raise DependencyError,
            "route #{route.path} references unknown dependency: #{reference}"
    end

    validate_dependency_reference(route, dependency, reference)
  end
end