Errors

Every error code of the delivery contract with its status, meaning and the right reaction, including the miss that never starts a build and the service-level answers.

  • Availability: Experimental
  • Evidence: Read from source
  • Reference

One envelope

Every failure has a stable code and an HTTP status, and travels as the same JSON body whichever service produces it:

JSON
{
  "error": {
    "code": "MODULE_NOT_FOUND",
    "message": "…",
    "diagnostics": [{ "code": "…", "message": "…" }]
  }
}

diagnostics is present only when there are any, which today means a failed build. Branch on code. The message is English technical text for troubleshooting and can change.

An error of a public request can be read by a page of another origin, like any public answer: it carries Access-Control-Allow-Origin: *, so a browser that failed to load a module lets the page read the code and the message. Errors are never stored (no-store).

JavaScripterrors.mjs
// Every failure is the same JSON envelope on every service. Branch on `code`,
// never on the message.
const origin = process.env.CDN_ORIGIN ?? process.env.DEV_ORIGIN;

// The options of the contract default to published delivery, so a development service refuses them with
// OPTION_UNSUPPORTED before it looks at the identity. A development consumer always asks for its output.
const development = !process.env.CDN_ORIGIN;
const options = development ? 'target=node&format=esm&env=development&min=false&sourcemap=inline' : 'target=browser&format=esm';

// A well-formed identity that no service holds
const request = `/m/@example/does-not-exist@0.0.1/modules/main?${options}`;

const response = await fetch(new URL(request, origin));
const type = response.headers.get('content-type') ?? '';

if (response.ok || !type.includes('application/json')) {
	console.log(`unexpected answer: ${response.status} ${type}`);
	process.exit(1);
}

const { error } = await response.json();
console.log(`${response.status} ${error.code}`);
console.log(error.message);
for (const diagnostic of error.diagnostics ?? []) console.log(`  ${diagnostic.code}: ${diagnostic.message}`);

if (error.code !== 'PACKAGE_NOT_FOUND') process.exitCode = 1;

Codes

Status Code Meaning What to do
400 IDENTITY_INVALID The path does not follow the grammar: a version that is not exact, an encoded slash, a dot segment, a bad asset path Fix the URL. Build paths with the shared codec.
400 OPTION_INVALID An option is unknown, repeated, missing when required, or has a value outside its list. An asset request with any query. Fix the query. See Request options.
400 OPTION_UNSUPPORTED The request is valid and this service cannot produce that output at all Request an output the service produces
401 ACCESS_REQUIRED The resource is not public and the request carries no access context See Private access
403 ACCESS_DENIED The access context does not allow the resource Obtain a valid grant, or ask an administrator of the application
404 PACKAGE_NOT_FOUND Nothing of this package has been prepared here. It says nothing about the package at its registry. Check the name and the registry prefix, or prepare a release that uses it
404 VERSION_MISMATCH Another version of the package has been prepared here, not this one Request the version that is served, or prepare a release with this one
404 MODULE_NOT_FOUND This package version is available here and does not publish a module with this subpath Check the subpath. Internal files are not public modules.
404 OUTPUT_NOT_AVAILABLE The identity is known and the service does not hold the requested output: an option set that was not prepared, a module without the requested companion, or an asset the package does not declare Prepare a release that includes it. Retrying changes nothing.
422 BUILD_FAILED Development only. The module exists and its current sources do not produce a valid artifact. Compiler diagnostics are included and no earlier output is served in its place. Fix the sources
501 SOURCE_UNSUPPORTED The service does not deliver this module source. A development server answers it for /m/git/…, /m/digest/… and for a package installed from Git or from an archive. Load that package from an origin that delivers its source, such as a CDN release that pinned it
404 NOT_FOUND Service level: the path addresses nothing this service serves at all, such as another route, a host without an application, or /resolution.json on the shared delivery origin Check the base origin and the route
503 UNAVAILABLE Service level: the service cannot answer now, for example because its storage does not answer Retry later. It says nothing about the resource.
500 INTERNAL Service level: an unexpected failure. Its message does not repeat the one of the failure. Retry, and report it if it persists

The last three are the answer a service gives, on a route of the contract, to a failure the contract has no code for, so a consumer never meets the vocabulary of one implementation.

A miss is not a build request

Every miss describes what this CDN has prepared and retained, and only that. None of them is a claim about the package at its registry, a promise that asking again will build something, or a description of what another tenant prepared.

OUTPUT_NOT_AVAILABLE is the answer of a retrieval-only service. It means the service looked, found nothing, and did nothing else:

  • no compilation was started, resumed or queued;
  • the answer is not OPTION_UNSUPPORTED, because the service could hold the output;
  • the response is never immutable, because the output can be prepared later.
JavaScriptmiss.mjs
// A published service only retrieves. A valid request for an output that was
// not prepared is a 404 with OUTPUT_NOT_AVAILABLE, and asking again changes
// nothing: no GET starts a build.
const origin = process.env.CDN_ORIGIN;

// Set UNPREPARED_REQUEST to a valid option set that your release did not prepare
const request = process.env.UNPREPARED_REQUEST ?? '/m/@example/shared@1.0.0/modules/text?target=node&format=cjs';

for (const attempt of [1, 2]) {
	const response = await fetch(new URL(request, origin));
	const { error } = await response.json();
	console.log(`attempt ${attempt}: ${response.status} ${error.code}`);
	console.log(`  Cache-Control: ${response.headers.get('cache-control')}`);

	const acceptable = ['OUTPUT_NOT_AVAILABLE', 'OPTION_UNSUPPORTED'].includes(error.code);
	if (!acceptable) process.exitCode = 1;
}

On a development server the same situation is handled differently on purpose: a known module is compiled from its current sources when requested, and BUILD_FAILED reports sources that do not compile.

Situation Development server Published delivery
Known module, output not held Compiles now 404 OUTPUT_NOT_AVAILABLE
Sources do not compile 422 BUILD_FAILED with diagnostics Not applicable: the failure belongs to the preparation job, and the active release is untouched
Valid option the service never produces 400 OPTION_UNSUPPORTED 400 OPTION_UNSUPPORTED

Errors of output selection

Choosing the wrong output of a public module in source code, such as importing a style-only module without .css, is not a delivery error: it is found when the importing module is compiled or analyzed, and it reaches you as a diagnostic of that build on a development server, or as a failed preparation job on the CDN. The diagnostics are OUTPUT_NOT_FOUND, OUTPUT_AMBIGUOUS and STYLE_BINDING_UNSUPPORTED; see Selecting an output.

In code

JavaScript
import { ContractError } from '@beyond-js/artifact-api';

const response = await fetch(url);
if (!response.ok) {
	const error = ContractError.from(response.status, await response.json());
	error.code; // 'OUTPUT_NOT_AVAILABLE'
	error.status; // 404
	error.diagnostics; // compiler diagnostics of a failed build, when present
}

ContractError.from returns undefined when the body is not an error envelope of this contract, for example the HTML error page of a proxy.