Preparation and inventory

Stages 2 and 3. Prepare a candidate release from a pinned registration, inspect the inventory of what is reachable, and understand readiness.

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

What preparation does

Preparing a pinned registration creates a candidate release and a durable job with four stages:

Stage Work
prepare Download every package of the pinned graph that the CDN does not already hold. Verify the archive against the pinned integrity, and enforce compressed size, extracted size and entry count while downloading and extracting.
analyze Trace what is reachable from the entries of the targets and persist the inventory. No output is generated in this stage.
generate Queue only the outputs that are missing. A compatible output that an authorized scope already holds is reused without new execution.
validate Check that the whole required serving closure is durable and retrievable. Only then does the release become ready.

The active release never changes during preparation, whatever the outcome.

Operations

Operation Request Capability Retry
registrations.prepare POST /v1/applications/{application}/registrations/{registration}/prepare release.prepare key
releases.inventory GET /v1/applications/{application}/releases/{release}/inventory application.read

Start a preparation

The body is optional:

Member Default Meaning
diagnostics false Run semantic TypeScript Diagnostics. Requires the diagnostics entitlement; without it the answer is 403 ENTITLEMENT_REQUIRED. Build errors are reported either way.
note Up to 500 characters

The answer is 202 with the candidate release and the job. A retry with the same Idempotency-Key answers the same pair.

Admission checks quotas, the organization's credit and the global budget together, before any work starts. It can refuse with QUOTA_EXCEEDED, CREDIT_INSUFFICIENT or BUDGET_EXHAUSTED. A registration that is not pinned answers 409 STATE_INVALID.

The complete sequence, from creating the application to a ready release:

JavaScriptprepare-release.mjs
// Register an application, pin its dependency graph and prepare a candidate
// release. Every step is an explicit request: nothing here is triggered by a GET.
const api = process.env.CDN_API_ORIGIN;
const token = process.env.CDN_TOKEN;
const organization = process.env.CDN_ORGANIZATION;

// Reusing the label makes a retry of this script safe: the same Idempotency-Key
// with the same request answers the original result instead of repeating the work.
const label = process.env.RUN_LABEL ?? 'docs-example-0001';

async function call(method, path, body, key) {
	const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };
	if (key) headers['Idempotency-Key'] = `${label}.${key}`;

	const response = await fetch(new URL(path, api), { method, headers, body: body && JSON.stringify(body) });
	const document = response.status === 204 ? undefined : await response.json();
	if (!response.ok) throw new Error(`${method} ${path}: ${response.status} ${document.error.code}${document.error.message}`);
	return document;
}

async function finished(job) {
	const final = ['succeeded', 'failed', 'cancelled', 'limit_exceeded'];
	while (!final.includes(job.state)) {
		console.log(`  job ${job.id}: ${job.state}${job.stage ? ` (${job.stage})` : ''}${job.queue ? `, queue position ${job.queue.position}` : ''}`);
		await new Promise(resolve => setTimeout(resolve, 2000));
		job = await call('GET', `/v1/jobs/${job.id}`);
	}
	if (job.state !== 'succeeded') throw new Error(`job ${job.id} ended ${job.state}: ${job.failure.code}${job.failure.message}`);
	return job;
}

const application = await call('POST', `/v1/organizations/${organization}/applications`, { name: 'docs-example' }, 'application');
console.log(`application ${application.id}`);

// The entry is a public subpath of the package, never a source file
await call('PUT', `/v1/applications/${application.id}/targets/web`, {
	kind: 'frontend',
	package: '@example/app',
	selection: '1.0.0',
	entry: '.'
});

// Stage 1: register and resolve. Metadata only; no archive is downloaded.
const registered = await call('POST', `/v1/applications/${application.id}/registrations`, {}, 'registration');
await finished(registered.job);

// Stages 2 and 3: download, analyze, generate what is missing, validate the closure
const prepared = await call('POST', `/v1/applications/${application.id}/registrations/${registered.registration.id}/prepare`, {}, 'preparation');
await finished(prepared.job);

const release = await call('GET', `/v1/applications/${application.id}/releases/${prepared.release.id}`);
console.log(`release ${release.id} #${release.number}: ${release.state}, closure ${release.readiness.durable}/${release.readiness.required}`);

The inventory

The inventory is available as soon as analysis completes, before and without generation. It lists every reachable item of the release with its state on the CDN.

Item member Values
kind module, style, asset
package, subpath What the item is
loading eager or lazy
targets The targets that reach it
key The compatibility key of the output
state available (a compatible authorized output already existed), missing (queued for generation), generated, failed, limit_exceeded
artifact The content digest of the output, once it exists

counts totals the items by state, and adds unknown: the number of undeclared dynamic imports.

Compatibility keys are not content digests

The key of an item identifies the recipe: the module, the integrity of its sources, its slice of the resolution, the compiler identity with its version and configuration, the conditions, the format and the output kind. The artifact digest identifies the resulting bytes. A package version alone is never enough to decide that two outputs are interchangeable.

Reuse also respects access. Two technically equal outputs are shared only when the consumer is authorized for the scope that holds them. Public dependencies stay public; private sources and outputs stay inside their organization.

Dynamic imports and the limit of static tracing

Reachability covers eager and lazy public modules, styles as modules and declared static assets. It comes from static tracing, which cannot follow an import whose target is computed at run time.

Such an import is reported in unknown. While counts.unknown is above zero the closure is never complete, and the release does not become ready: the job ends with DYNAMIC_IMPORT_UNKNOWN, before anything is persisted.

Declare the possible targets so that analysis can include them: in the module manifest of the package, or, for a package you cannot change, in the declared member of the registration. A declared import is followed as a lazy reference and reported as DYNAMIC_IMPORT_DECLARED.

Readiness

A release is ready when its readiness reports complete: true, which means durable equals required: every output of the required serving closure is stored and retrievable. A finished worker or one stored JavaScript file is not readiness. A missing required resource or an unresolved entry ends the job with CLOSURE_INCOMPLETE.

Semantic Diagnostics

Essential build errors are always reported, on every plan: a module that does not compile fails with BUILD_FAILED and its compiler diagnostics. Entitlement never hides a failure you need in order to understand an unsuccessful build.

Real semantic TypeScript Diagnostics, with file, range and code, is a premium capability. Its duration is measured in its own usage category, diagnostics.