URL e identidades

La gramática de la ruta de un módulo público compilado, las fuentes que nombra una identidad (registros, repositorios Git y digests de archivos comprimidos) y la regla que permite que una misma URL relativa funcione en cualquier origen.

  • Disponibilidad: Experimental
  • Evidencia: Leído del código fuente
  • Referencia

La ruta

Text
/m/[<registry>/]<package>@<version>/<family>/<subpath>
/m/git/<host>/<owner>/<repo>@<commit>/<family>/<subpath>
/m/digest/<algorithm>-<hex>/<family>/<subpath>

https://cdn.beyondjs.com/m/@example/app@1.0.0/modules/core/router?target=browser&format=esm

https://cdn.beyondjs.com
Origen base: lo único que cambias
/m/
Espacio de nombres
@example/app
Paquete
@1.0.0
Versión exacta
/modules/
Familia de recursos
core/router
Subruta del módulo público
?target=browser&format=esm
Opciones
Parte Regla
/m/ El espacio de nombres del contrato. Toda ruta de recurso comienza con él.
<registry>/ Opcional. npm es el registro predeterminado y no se escribe: la forma sin prefijo es la canónica, y /m/npm/… es un alias aceptado de la misma identidad. Cualquier otro id de registro (minúsculas, dígitos, ., _, -) forma parte de la identidad, de modo que dos registros nunca comparten una clave. git y digest nunca son ids de registro.
<package> El nombre del paquete, con ámbito (@example/app) o sin él (library).
@<version> Una versión semántica exacta, opcionalmente con partes de prelanzamiento y de compilación: 1.0.0, 2.1.0-beta.1. Los rangos y las etiquetas no son identidades. Quien resuelve un rango solicita la versión exacta que eligió.
/<family>/ La familia de recursos: /modules/, /styles/, /maps/ o /assets/; consulta Salidas, estilos y recursos.
<subpath> La subruta del módulo público dentro del paquete, sin el ./ inicial. Una subruta anidada conserva sus barras sin codificar: core/router.

El módulo raíz

El módulo público raíz de un paquete, el que importas solo con el nombre del paquete, se escribe con el segmento reservado ~root:

Text
/m/@example/app@1.0.0/modules/~root

Los analizadores de URL eliminan . y los segmentos vacíos, y ~ no puede aparecer en la subruta de un módulo, así que el segmento es reversible y no puede chocar con un módulo real.

Qué se rechaza

Una identidad tiene exactamente una ruta. Todo lo siguiente es 400 IDENTITY_INVALID:

  • una barra o barra invertida codificada (%2f, %5c), una barra invertida, o un segmento . o ..;
  • una versión que no es exacta, o una ruta sin @<version>;
  • una ruta sin /<family>/<subpath>;
  • un nombre de paquete, una subruta, un host, un commit o un digest con caracteres fuera de la gramática.

Fuentes

La ruta indica de dónde viene un paquete. Dos paquetes con el mismo nombre y la misma versión de fuentes distintas tienen rutas, claves, fuentes almacenadas y salidas distintas.

Fuente Prefijo de la ruta Clave del paquete Qué la fija
El registro de npm /m/<package>@<version> npm:<package>@<version> La versión exacta
Otro registro compatible con npm /m/<registry id>/<package>@<version> <registry id>:<package>@<version> La versión exacta en ese registro
Un repositorio Git /m/git/<host>/<owner>/<repo>@<commit> git:<host>/<owner>/<repo>@<commit> Un commit completo de 40 caracteres. Una rama o una etiqueta se fija a su commit cuando se resuelve el grafo.
Una URL de archivo comprimido /m/digest/<algorithm>-<hex> digest:<algorithm>-<hex> El digest del contenido, sha256 o sha512. Una dirección sin fragmento de integridad se fija descargándola una vez cuando se resuelve el grafo.
  • El id de un registro lo escribe Beyond Packages cuando resuelve un paquete, tanto en el CDN como en un servidor de desarrollo: registry-<slug>-<32 hexadecimal digits>, un slug legible de la dirección del registro seguido de 128 bits de su digest SHA-256, de modo que ninguna dirección puede elegirse para parecerse a otro registro. Léelo de la resolución o del grafo; nunca lo construyas.
  • Una fuente Git es la raíz del repositorio. Las subrutas de un repositorio y git+ssh: no son fuentes.
  • Una identidad Git o por digest no lleva nombre de paquete en su ruta, así que no tiene especificador público: el código sigue importando el paquete por el nombre que declara su manifiesto, y la resolución asigna ese nombre a la ruta.
  • Un nombre y una versión por aplicación. Dentro de una aplicación, un name@version debe venir de una sola fuente: el runtime registra un módulo cargado por nombre, versión y subruta. Preparar una aplicación que necesita el mismo nombre y la misma versión de dos fuentes falla con UNSUPPORTED_INPUT. Entre aplicaciones y organizaciones, las fuentes nunca chocan.

Qué fuentes entrega cada servicio está en la tabla de capacidades. Un servicio responde 501 SOURCE_UNSUPPORTED para una identidad bien formada de una fuente que no entrega.

Qué importa un consumidor

Una ruta indica cómo se obtiene un módulo, no cómo se importa. El código de la aplicación importa el especificador público, que nunca lleva versión:

Forma Ejemplo Se usa para
Especificador @example/app/core/router Lo que importa el código fuente
Especificador con versión @example/app@1.0.0/core/router La identidad que el artefacto registra en el runtime
Clave npm:@example/app@1.0.0/core/router Tablas de resolución y cachés; incluye la fuente

Un documento de resolución asigna especificadores a rutas, para que el código de la aplicación no contenga versiones ni orígenes.

La misma URL relativa en cualquier origen

El mismo módulo es la misma ruta relativa y la misma consulta en todos los servicios que implementan el contrato. Un consumidor cambia solo el origen base: el host y, en local, el esquema y el puerto.

Text
http://localhost:<port>/m/@example/app@1.0.0/modules/core/router?target=browser&format=esm
https://cdn.beyondjs.com/m/@example/app@1.0.0/modules/core/router?target=browser&format=esm

Es compatibilidad de solicitudes, respuestas y errores, no solo de nombres. Lo que difiere entre servicios es deliberado y acotado: qué salidas tiene cada uno, sus reglas de acceso y su Cache-Control. Ninguna de esas diferencias cambia una URL.

Un servicio al que se llega a través del enrutamiento de un entorno alojado puede tener un prefijo de ruta como parte de su origen base. La parte relativa que sigue no cambia. Una aplicación publicada es el caso principal: /_r/<release number> en su host es un origen base propio; consulta el prefijo del release.

Toda respuesta pública, errores incluidos, puede leerla una página de otro origen: lleva Access-Control-Allow-Origin: * y expone ETag, SourceMap y Link. Así una página sabe por qué falló un módulo, no solo que falló. Un recurso privado y los rechazos ACCESS_REQUIRED y ACCESS_DENIED dependen de quién pregunta y no permiten cualquier origen.

Leer y escribir rutas desde el código

No analices ni construyas estas URL a mano. @beyond-js/artifact-api es la única implementación de la gramática:

JavaScript
import { ModulePath, ResourcePath, Identity } from '@beyond-js/artifact-api';

const identity = ModulePath.parse('/m/@example/app@1.0.0/modules/core/router');
identity.specifier; // '@example/app/core/router'
identity.vspecifier; // '@example/app@1.0.0/core/router'
identity.key; // 'npm:@example/app@1.0.0/core/router'
ModulePath.format(identity); // the canonical path, reversible

// Git and digest sources are read only by a caller that implements them
const fork = ModulePath.parse('/m/git/github.com/example/app@<commit>/modules/core/router', Identity.sources);
fork.source; // 'git'
fork.key; // 'git:github.com/example/app@<commit>/core/router'
fork.specifier; // undefined: the path carries no package name

const resource = ResourcePath.parse('/m/@example/app@1.0.0/styles/core/router');
resource.kind; // 'style'

ModulePath.parse lanza un ContractError con el código IDENTITY_INVALID, o SOURCE_UNSUPPORTED para una identidad bien formada de una fuente que quien llama no listó. El paquete no tiene dependencias en tiempo de ejecución e importarlo no tiene efectos secundarios.