jsbundle

package
v0.7.23 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 23, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package jsbundle resolves and stores the JS/TS bundles that back runtime=isolate sandboxes (plans/isolate-runtime.md §7, the analog of pkg/wasmmod for .wasm modules). A "bundle" is the code a workerd isolate runs: a set of ES modules plus the name of the entry module and the pinned workerd compatibility date. Bundles are content-addressed — the sha256 over a canonical serialization is the identity that makes create idempotent (a retry resolves to the same digest and joins the existing sandbox) and lets the store deduplicate and reference-count.

Index

Constants

View Source
const DefaultCompatibilityDate = "2026-01-01"

DefaultCompatibilityDate pins workerd's behavior for a bundle that does not name one. Bundles SHOULD pin their own — this is the floor so an engine upgrade never silently changes a bundle's semantics (plans/isolate-runtime.md §9). Kept conservative and bumped deliberately.

View Source
const DefaultMainModule = "main.js"

DefaultMainModule is the entry module name assumed when a bundle is resolved from a single source file.

Variables

View Source
var (
	// ErrInvalidBundle is a structural validation failure (missing main
	// module, empty source, no compatibility date).
	ErrInvalidBundle = errors.New("jsbundle: invalid bundle")
	// ErrBundleNotFound is returned when a ref (digest or uploaded name) does
	// not resolve to a stored bundle.
	ErrBundleNotFound = errors.New("jsbundle: bundle not found")
	// ErrBundleTooLarge is returned when a bundle exceeds the store's size cap
	// (abuse control, plans/isolate-runtime.md §8).
	ErrBundleTooLarge = errors.New("jsbundle: bundle exceeds size cap")
	// ErrTenantQuotaExceeded is returned when storing a bundle would push a
	// tenant past its bundle-count quota (abuse control).
	ErrTenantQuotaExceeded = errors.New("jsbundle: tenant bundle quota exceeded")
	// ErrUnsupportedRef is returned for a reference shape the resolver does
	// not understand.
	ErrUnsupportedRef = errors.New("jsbundle: unsupported bundle reference")
)

Functions

func IsFileRef

func IsFileRef(ref string) bool

IsFileRef reports whether ref is a filesystem/file:// entrypoint (as opposed to a digest or an uploaded name). The service uses it to gate host-filesystem reads to operator/unscoped callers — a scoped tenant must not be able to make the daemon read an arbitrary host file via module_ref:"file:///…".

Types

type Bundle

type Bundle struct {
	// MainModule is the entry module name; it MUST be a key in Modules.
	MainModule string `json:"main_module"`
	// Modules maps module name → ES module source. At least one entry.
	Modules map[string]string `json:"modules"`
	// CompatibilityDate pins workerd behavior (Workers versioning).
	CompatibilityDate string `json:"compatibility_date"`
	// Digest is the sha256 (hex, no prefix) over the canonical serialization;
	// set by ComputeDigest / the store, empty on a freshly-built bundle.
	Digest string `json:"-"`
}

Bundle is the resolved code for one isolate: the module map (name → source), the entry module, and the pinned compatibility date. It is exactly the shape the workerd controller's dynamic-load provider needs (§2.2 spike): the host wrapper serializes this into the WorkerLoader source.

func BuildFromFile

func BuildFromFile(path string) (*Bundle, error)

BuildFromFile turns a single JS/TS entrypoint file into a one-module bundle. This is the Phase-2 hot-path default: operators point module_ref at a .js file and it becomes the bundle verbatim (no transpile step — workerd runs modern JS and TS type-strips at load with the right compatibility flags). Multi-module / import-following builds (esbuild, §13 Q2) are a later seam; the single-file path covers the "AI-generated JS tool" and "webhook transform" use cases that motivate the tier without a build toolchain on the host.

func BuildFromSource

func BuildFromSource(name, source, compatDate string) (*Bundle, error)

BuildFromSource turns inline entry-module source into a one-module bundle (the /v1/js-bundles push path and tests). name defaults to DefaultMainModule when empty; compatDate defaults to DefaultCompatibilityDate.

func (*Bundle) ComputeDigest

func (b *Bundle) ComputeDigest() (string, error)

ComputeDigest fills Digest from the canonical serialization and returns it.

func (*Bundle) SizeBytes

func (b *Bundle) SizeBytes() int64

SizeBytes is the total source size across modules — the quantity the store's size cap and per-tenant quota are enforced against.

func (*Bundle) Validate

func (b *Bundle) Validate() error

Validate enforces the invariants every stored/injected bundle must hold: a non-empty main module that exists in a non-empty module map, and a compatibility date. It does NOT validate JS syntax — workerd is the arbiter of that at load time; this is the cheap structural gate before we persist or inject.

type Resolver

type Resolver struct {
	// contains filtered or unexported fields
}

Resolver turns a bundle reference into a concrete Bundle. It is the production BundleResolver behind the isolate driver (adapted in internal/runtime/isolate). Reference shapes, in precedence order:

  • "sha256:<hex>" or a bare 64-hex string — a content digest; loaded from the store. This is the idempotent create path: the sandbox row pins the digest, so a retry or a failover peer resolves the exact same bytes.
  • "file://<path>" or a filesystem path ending .js/.mjs/.ts — an operator/ self-host entrypoint file, built into a one-module bundle.
  • any other non-empty token — an uploaded bundle NAME, looked up in the store for the resolving tenant (the "no image, no registry" remote path via POST /v1/js-bundles).

The store may be nil for an operator-only deployment that resolves file paths exclusively; name/digest refs then return ErrBundleNotFound.

func NewResolver

func NewResolver(store *Store) *Resolver

NewResolver builds a resolver over an optional content-addressed store.

func (*Resolver) Resolve

func (r *Resolver) Resolve(ctx context.Context, tenant, ref string) (*Bundle, error)

Resolve resolves ref for tenant (tenant scopes uploaded-name lookups; it is ignored for digest and file refs). The returned bundle always has Digest set.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is a content-addressed bundle store backing POST /v1/js-bundles (plans/isolate-runtime.md §8). Bundles are keyed by digest on disk; uploaded names are pointers to a digest, and a per-tenant index bounds how many distinct bundles one tenant may hold. It ships with the abuse controls the plan requires from day one: a per-bundle size cap and a per-tenant bundle-count quota. GC of unreferenced bundles is reference-counted by the caller (the service knows which digests live sandboxes pin); the store exposes Delete for it.

The store is process-local and guarded by a single mutex — bundle writes are rare (a push), not on the sandbox hot path, so contention is a non-issue and a plain lock keeps the invariants (digest file + name pointer + tenant index stay consistent) obvious.

func NewStore

func NewStore(cfg StoreConfig) (*Store, error)

NewStore opens (creating if needed) a content-addressed bundle store rooted at cfg.Dir and rebuilds its in-memory name/tenant indexes from disk so pushed bundles survive a daemon restart.

func (*Store) Delete

func (s *Store) Delete(tenant, digest string) error

Delete removes tenant's ownership of a digest: it drops tenant's name pointers to it and its entry in tenant's ownership list, and removes the shared blob ONLY when no other tenant still owns it. Blobs are content- addressed and DEDUPLICATED across tenants, so a tenant deleting bytes it happens to share with another tenant must not evict the other tenant's bundle. Returns ErrBundleNotFound when tenant does not own the digest so DELETE /v1/js-bundles/{digest} 404s (matching /v1/wasm-modules) instead of silently succeeding on an unknown or unowned id.

func (*Store) GCUnreferenced

func (s *Store) GCUnreferenced(pinned map[string]struct{}) ([]string, error)

GCUnreferenced deletes every digest that is neither pinned by a live sandbox nor referenced by an uploaded catalogue name. Named uploads with no live sandbox are kept — the catalogue IS a reference. Returns digests removed.

func (*Store) GetByDigest

func (s *Store) GetByDigest(digest string) (*Bundle, error)

GetByDigest loads a bundle by its content digest.

func (*Store) GetByName

func (s *Store) GetByName(tenant, name string) (*Bundle, error)

GetByName resolves an uploaded name (scoped to tenant) to its bundle.

func (*Store) ListDigests

func (s *Store) ListDigests(tenant string) []string

ListDigests returns the content digests a tenant owns (the catalogue GET scope for POST /v1/js-bundles). Order is unspecified.

func (*Store) NamesForTenant

func (s *Store) NamesForTenant(tenant string) map[string]string

NamesForTenant returns the uploaded name → digest pointers a tenant holds.

func (*Store) Put

func (s *Store) Put(tenant, name string, b *Bundle) (string, error)

Put stores a bundle content-addressed by digest and, when name is non-empty, points that uploaded name at the digest for the given tenant. Storing an identical bundle again is idempotent (same digest → same blob), and re-using a name just repoints it. tenant "" is the operator/self-host null tenant and is exempt from the per-tenant quota. Returns the digest.

func (*Store) TenantOwns

func (s *Store) TenantOwns(tenant, digest string) bool

TenantOwns reports whether tenant owns digest — the ownership gate for GET/DELETE /v1/js-bundles/{digest} so one tenant cannot read or delete another's bundle by guessing a digest.

type StoreConfig

type StoreConfig struct {
	Dir          string
	MaxBytes     int64
	PerTenantMax int
}

StoreConfig configures the abuse controls. Zero maxBytes / perTenantMax mean unlimited on that axis (operator/self-host default); the managed control plane sets real caps.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL