Resolution
The beyond-resolution/1 document and the import map every origin answers at /resolution.json and /importmap.json, mapping each public specifier to an origin-relative URL, with scopes for coexisting versions.
- Availability: Experimental
- Evidence: Reproduced run
- Reference
What it is
A beyond-resolution/1 document says which URL delivers each public specifier, for one target and one format. Every value is origin-relative: a canonical path plus the canonical query. One document therefore works on a development origin and on a published one, and a consumer prepends the origin it was configured with.
{
"protocol": "beyond-resolution/1",
"release": "r-42",
"target": "browser",
"format": "esm",
"imports": {
"@example/app": "/m/@example/[email protected]/modules/~root?target=browser&format=esm&env=production&min=true&sourcemap=external&types=false&css=false"
},
"scopes": {
"/m/@example/[email protected]/": {
"library/main": "/m/[email protected]/modules/main?target=browser&format=esm&env=production&min=true&sourcemap=external&types=false&css=false"
}
}
}Members
| Member | Required | Rule |
|---|---|---|
protocol |
Yes | beyond-resolution/1 |
release |
No | The release the document belongs to. Opaque to the contract. Development output has none. |
target |
Yes | browser or node |
format |
Yes | esm, cjs or system |
imports |
Yes | Origin-relative URL by public specifier |
scopes |
Yes | For each scope prefix, the imports that override imports for the importers under it. May be empty. |
Rules
- A specifier is one public module and is matched exactly. There are no prefix mappings. It resolves to a module path, or to a style path only when the specifier ends with
.css(output selection). A specifier without.cssnever resolves to a stylesheet. - A value is accepted only when the shared codec reads it and writes it back unchanged: the canonical path (no
/m/npm/alias), the canonical query, and thetargetandformatof the document. A scheme, a host, a fragment or a missing query is rejected. - A scope is an origin-relative path under
/m/. A scope that ends with/matches every importer under it; any other scope matches one path. The longest scope that lists the specifier wins, andimportsanswers otherwise. - A document that breaks a rule is rejected with
RESOLUTION_INVALID. This is an error of the client-side codec; it is not one of the HTTP error codes of the delivery routes.
Scopes are how two versions of one package coexist in one application: the modules of @example/legacy above keep [email protected] while everyone else gets the version in imports.
The routes
Every origin of the contract answers the resolution it serves, in two forms:
GET <base>/resolution.json[?target=<browser|node>&format=<esm|cjs|system>] beyond-resolution/1, application/json
GET <base>/importmap.json[?target=<browser|node>&format=<esm|cjs|system>] import map, application/importmap+jsontargetandformatgo together, or neither: without them the service answers its default document. Any other option, a repeated one, an invalid value or one of the two alone is400 OPTION_INVALID.importmap.jsonis the same resolution as a standard import map whose every address and scope key is relative to the document (./m/…). A consumer that loads the map from its URL resolves it on the origin that served it, path prefix included: Deno with--import-map=<base>/importmap.json?…, and SystemJS with<script type="systemjs-importmap" src="…">. A page that inlines a map resolves addresses against itself, so it needs absolute addresses:resolution.importmap(origin).- The answers carry a strong
ETag, answerIf-None-Matchwith304, and can be read by a page of another origin, like every resource of the contract.
| Development server | CDN | |
|---|---|---|
| Where | The origin of the service | The base of one release, /_r/<release number>, and on an application host the alias of the bound release. The shared delivery origin has none (404 NOT_FOUND). |
| What | Computed from the workspace on every request, no-store. For browsers it also lists the installed packages and stylesheets that the builds reach; the Node document lists the modules of the workspace only. |
The documents stored when the release was prepared, one per target and format, under the release's cache policy. They never change. |
| Without a query | The Node ES module document | The document of the default target |
| A target or format it does not have | 400 OPTION_UNSUPPORTED (format=cjs) |
404 OUTPUT_NOT_AVAILABLE, never a build |
Using it
This example is executed by the verification harness of this site against the shared codec:
// Read a beyond-resolution/1 document with the shared codec. Its values are
// origin-relative, so the same document serves any base origin.
import { Resolution } from '@beyond-js/artifact-api';
const query = 'target=browser&format=esm&env=production&min=true&sourcemap=external&types=false&css=false';
const resolution = new Resolution({
protocol: 'beyond-resolution/1',
release: 'r-42',
target: 'browser',
format: 'esm',
imports: {
'@example/app': `/m/@example/[email protected]/modules/~root?${query}`,
'library/main': `/m/[email protected]/modules/main?${query}`,
// Only a specifier that ends with ".css" resolves to a stylesheet
'@example/app/theme.css': `/m/@example/[email protected]/styles/theme?${query}`
},
scopes: {
// Modules of @example/legacy keep the version of "library" they were tested with
'/m/@example/[email protected]/': { 'library/main': `/m/[email protected]/modules/main?${query}` }
}
});
const importer = '/m/@example/[email protected]/modules/widget';
console.log(resolution.resolve('library/main'));
console.log(resolution.resolve('library/main', importer));
console.log(resolution.url('https://cdn.example', '@example/app'));
console.log(resolution.resolve('@example/app/theme.css'));
// Absolute addresses, for a map inlined in a page of another origin
console.log(JSON.stringify(resolution.importmap('http://localhost:8080'), null, 2));
// Addresses relative to the document, as a service answers /importmap.json
console.log(JSON.stringify(resolution.importmap(), null, 2));| Call | Result |
|---|---|
new Resolution(values), Resolution.parse(text) |
Validates the document; throws RESOLUTION_INVALID |
resolve(specifier, importer?) |
The origin-relative URL, or undefined. The importer is an origin-relative URL or path. |
url(origin, specifier, importer?) |
The absolute URL on one service |
importmap(origin) |
{ imports, scopes } with absolute URLs, for a map inlined in a page |
importmap() |
{ imports, scopes } relative to a document at the base (./m/…), which is what /importmap.json answers |
serialize() |
The document with a stable key order, so equal resolutions serialize equally |
ResolutionRequest.parse(pathname, query) reads a request of either route and says which target and format it selects, for a service that implements them.
A release never re-resolves
A release carries its resolution. The management API reports its location as urls.resolution of the release, as an origin-relative URL, and the backend closure of a target embeds the frozen resolution of that target. The resolution of a release never changes: promotion and rollback select a release, they do not re-resolve it.
Each environment consumes these documents in its own way; see Strategies per environment.