Lightning.Adaptors.Strategy behaviour (Lightning v2.19.0)

View Source

Behaviour every adaptor strategy implements.

A strategy is the sole boundary between the Lightning.Adaptors.* subsystem and the outside world. It defines four callbacks:

  • fetch_adaptor/1 returns a adaptor_record/0 for one package name. Icon fields are not part of this record. The Scheduler stamps them on separately after joining the bulk icon pipeline.
  • fetch_icon/2 returns the raw bytes and extension of one icon variant. The Store calls it when an icon is missing on disk.
  • fetch_icons/1 bulk-fetches icons for every adaptor the strategy knows. The Scheduler runs it once per tick in parallel with its per-adaptor fan-out. See the callback docs for :prior_etags.
  • list_adaptors/0 is the cheap change signal: name and latest_version for every package the strategy knows, which the Scheduler diffs against the adaptors table.

The active strategy module is resolved at runtime via Lightning.Adaptors.Config.strategy/0. Implementations surface transient failures (5xx, timeout, nxdomain) as {:error, term()} and do not retry.

Summary

Types

The record returned by fetch_adaptor/1. Icon fields are not on it. The Scheduler persists them separately after joining fetch_icons/1.

Fresh-fetch icon entry inside the fetch_icons/1 result map. :etag is the upstream cache validator verbatim from the HTTP response, nil when upstream sent none. Strategies with no transport-level validator, such as Lightning.Adaptors.Local, omit the key entirely.

Per-shape value inside the fetch_icons/1 result map. Either a fresh icon_entry/0 (a 200) or :not_modified (a 304), which is only ever returned when the caller supplied a prior etag via :prior_etags.

Bulk icon map returned by fetch_icons/1. A shape that is absent means upstream has no such icon. :not_modified means it is unchanged since the prior etag. A map is a fresh fetch to apply.

Per-version metadata extracted from an upstream packument or local package.json.

Callbacks

Fetch the full structured record for a single adaptor package.

Fetch the raw bytes for one icon variant (:square or :rectangle) of an adaptor package, together with the file extension.

Bulk fetch every available icon for every adaptor known to the strategy.

Cheap change signal: name and latest_version for every package the strategy knows. The Scheduler diffs this against the adaptors table to compute its work list.

Functions

Validate a schema body and pair it with its persisted digest.

Types

adaptor_record()

@type adaptor_record() :: %{
  name: String.t(),
  description: String.t() | nil,
  homepage: String.t() | nil,
  repository: String.t() | nil,
  license: String.t() | nil,
  latest_version: String.t(),
  deprecated: boolean(),
  schema_data: String.t() | nil,
  schema_sha256: String.t() | nil,
  versions: [version_record()]
}

The record returned by fetch_adaptor/1. Icon fields are not on it. The Scheduler persists them separately after joining fetch_icons/1.

schema_data is the credential schema as a JSON binary. nil means the source sees no schema for this version; the Scheduler decides whether that replaces a stored one.

icon_entry()

@type icon_entry() :: %{
  :data => binary(),
  :ext => String.t(),
  :sha256 => binary(),
  optional(:etag) => String.t() | nil
}

Fresh-fetch icon entry inside the fetch_icons/1 result map. :etag is the upstream cache validator verbatim from the HTTP response, nil when upstream sent none. Strategies with no transport-level validator, such as Lightning.Adaptors.Local, omit the key entirely.

icon_shape_value()

@type icon_shape_value() :: icon_entry() | :not_modified

Per-shape value inside the fetch_icons/1 result map. Either a fresh icon_entry/0 (a 200) or :not_modified (a 304), which is only ever returned when the caller supplied a prior etag via :prior_etags.

icons_map()

@type icons_map() :: %{
  required(String.t()) => %{
    optional(:square) => icon_shape_value(),
    optional(:rectangle) => icon_shape_value()
  }
}

Bulk icon map returned by fetch_icons/1. A shape that is absent means upstream has no such icon. :not_modified means it is unchanged since the prior etag. A map is a fresh fetch to apply.

version_record()

@type version_record() :: %{
  version: String.t(),
  integrity: String.t() | nil,
  tarball_url: String.t() | nil,
  size_bytes: integer() | nil,
  dependencies: map(),
  peer_dependencies: map(),
  published_at: DateTime.t() | nil,
  deprecated: boolean()
}

Per-version metadata extracted from an upstream packument or local package.json.

Callbacks

fetch_adaptor(name)

@callback fetch_adaptor(name :: String.t()) :: {:ok, adaptor_record()} | {:error, term()}

Fetch the full structured record for a single adaptor package.

fetch_icon(name, arg2)

@callback fetch_icon(name :: String.t(), :square | :rectangle) ::
  {:ok, %{data: binary(), ext: String.t()}} | {:error, term()}

Fetch the raw bytes for one icon variant (:square or :rectangle) of an adaptor package, together with the file extension.

fetch_icons(opts)

@callback fetch_icons(opts :: keyword()) :: {:ok, icons_map()} | {:error, term()}

Bulk fetch every available icon for every adaptor known to the strategy.

Returns {:ok, icons_map} (see icons_map/0). A top-level {:error, term()} is only returned when the whole pipeline cannot proceed, for example when the list_adaptors/0 call inside the bulk implementation fails.

Options

  • :prior_etags - %{name => %{optional(:square | :rectangle) => etag}}, sent as If-None-Match per (name, shape). Defaults to %{}. Strategies with no transport-level cache validator, such as Lightning.Adaptors.Local, ignore it and never return :not_modified.

list_adaptors()

@callback list_adaptors() ::
  {:ok, [%{name: String.t(), latest_version: String.t()}]} | {:error, term()}

Cheap change signal: name and latest_version for every package the strategy knows. The Scheduler diffs this against the adaptors table to compute its work list.

{:ok, []} means the strategy looked and there is genuinely nothing there, which settles the Store's first-load gate. A strategy that cannot tell an empty source from an unreadable one must return {:error, _}: Lightning.Adaptors.Local reports an empty checkout as {:ok, []}, Lightning.Adaptors.NPM reports an empty org listing as {:error, :empty_listing}.

Functions

digest_schema(body)

@spec digest_schema(binary()) :: {:ok, {binary(), String.t()}} | {:error, term()}

Validate a schema body and pair it with its persisted digest.

Returns {:ok, {body, sha256_hex}} when body decodes as JSON, with sha256_hex lowercase hex, matching the adaptors.schema_sha256 column format. {:error, reason} when it doesn't decode.