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
- Variables
- func IsFileRef(ref string) bool
- type Bundle
- type Resolver
- type Store
- func (s *Store) Delete(tenant, digest string) error
- func (s *Store) GCUnreferenced(pinned map[string]struct{}) ([]string, error)
- func (s *Store) GetByDigest(digest string) (*Bundle, error)
- func (s *Store) GetByName(tenant, name string) (*Bundle, error)
- func (s *Store) ListDigests(tenant string) []string
- func (s *Store) NamesForTenant(tenant string) map[string]string
- func (s *Store) Put(tenant, name string, b *Bundle) (string, error)
- func (s *Store) TenantOwns(tenant, digest string) bool
- type StoreConfig
Constants ¶
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.
const DefaultMainModule = "main.js"
DefaultMainModule is the entry module name assumed when a bundle is resolved from a single source file.
Variables ¶
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 ¶
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 ¶
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 ¶
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 ¶
ComputeDigest fills Digest from the canonical serialization and returns it.
func (*Bundle) SizeBytes ¶
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 ¶
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 ¶
NewResolver builds a resolver over an optional content-addressed store.
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 ¶
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 ¶
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 ¶
GetByDigest loads a bundle by its content digest.
func (*Store) ListDigests ¶
ListDigests returns the content digests a tenant owns (the catalogue GET scope for POST /v1/js-bundles). Order is unspecified.
func (*Store) NamesForTenant ¶
NamesForTenant returns the uploaded name → digest pointers a tenant holds.
func (*Store) Put ¶
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 ¶
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 ¶
StoreConfig configures the abuse controls. Zero maxBytes / perTenantMax mean unlimited on that axis (operator/self-host default); the managed control plane sets real caps.