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:
{
"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).
// 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.
// 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
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.