Repository navigation
1.0.0
Added
-
Multi-core serving:
serve({ workers })(a number, or"auto":os.availableParallelism(), at most 16),dusk start --workers <n|auto>andDUSK_WORKERSfor the generatedserver.mjs. The process becomes the primary of anode:clusterpool that serves nothing, forks the workers, waits until each listens (a worker that dies before the pool is ready fails the start), replaces a crashed worker with an exponential delay (500 ms doubling to 30 s), stops with exit code 1 and a clear message when 5 workers crash within 30 s (supervise.maxCrashes,crashWindowMs), kills a worker that does not listen within 30 s, replaces every worker one by one onSIGUSR2orpool.restart()(the new worker listens before the old one drains, which first stops accepting and answers on its open connections withConnection: close), and onSIGTERM/SIGINTdrains the workers for up togracefulShutdownMs.ServerHandlegainsrole("single","worker","primary") andpool(PoolControl:workers,pids(),restart(),failed);workerInfo()andmarkProcessLocalStore(label)are exported from@shadow-js/dusk/node; outside production a worker sendsx-dusk-worker. The primary warns once that in-memory stores are per worker (dusk start --workerswhen a page setsrevalidate;servefor every store that callsmarkProcessLocalStore). Withoutworkers, or with 1, nothing of the pool is loaded and the server behaves as before.tools/loadgains--workers. -
Production observability, opt-in and loaded only when used: the
serverTiminghandler option (W3CServer-Timingwithtotal,route,loaders,renderand your own metrics; on in development, off in production;{ header, onTimings }; never replaces a header the response already has; for a streamed page the head carries the time to the shell andonTimingsgets the full render time),event.timing.measure/event.timing.addandtiming()(@shadow-js/dusk/server) with token-checked names (at most 64 characters, at most 20 per request) and descriptions reduced to safe ASCII, child spans for the loader chain (dusk.loaders,dusk.loader), the endpoint (dusk.page,dusk.api,dusk.rpc, ...), the render and the stream (dusk.render,dusk.page.stream) with OpenTelemetry attribute names (http.response.status_code,http.route,error.type) andrecordException, each ended exactly once also on abort and timeout, and themetricshandler option (createCounter().add,createHistogram().record;dusk.server.requestsandhttp.server.request.duration). Without a tracer the request path allocates no span. New types:ServerTimingOptions,RequestTimings,EventTiming,MetricsLike,CounterLike,HistogramLike;RequestEventgainstiming. Guide: Configuring a Dusk app, Observability. -
pwa()(@shadow-js/dusk/vite, opt-in): after the client build it writesmanifest.webmanifestfrom a typed manifest, an offline page (no script, no inline style) andsw.js, a service worker generated as TypeScript and compiled by the build with no runtime library. The worker precaches the build's hashed assets with a content-hash revision per file, reuses unchanged files from the previous build's cache, answers a failed navigation with an app shell or the offline page, and applies runtime rules per path glob (network-first,cache-first,stale-while-revalidate, with entry and age limits and a network timeout). Caches are named by build id and the other builds' caches are deleted on activate. It handles only same-originGETrequests inside the base, never a request withAuthorizationor a range, never/_dusk/**, and never keeps a response that is not a plain200, isno-storeorprivate, varies on cookies, or carries a nonce policy. A new version waits until the page asks. -
@shadow-js/dusk/features/pwa(opt-in subpath):registerServiceWorker(SSR-safe, scoped, waits forload, not in development unlessdev: true),useServiceWorkerUpdate()returning{ waiting, update }, andunregisterServiceWorker. Guide: Dusk: installable apps and offline. -
imageVariants()(@shadow-js/dusk/vite, opt-in): at build time it prepares content-hashed AVIF and WebP files at a width ladder (640, 960, 1280, 1920 by default) for the images ofpublic/that the source mentions, caches the encoded files between builds, warns once whensharpis missing and serves the originals, and warns about originals over a size budget.<Image>writes a<picture>whose sources point at the prepared files, preloads the best prepared format for apriorityimage, and keeps the optimizer URLs on the<img>as the fallback. -
defineFontsgainsroutes(preload the files of some families on some routes only;fontPreloadsForpicks the tags for a path),report(andFontsOutput.report,formatFontReport): every font file with its size and whether every page preloads it. -
@shadow-js/dusk/auth(opt-in subpath, no runtime dependency): provider-agnostic sign-in for Auth0, Okta, Keycloak, Google, Microsoft Entra, GitHub, Discord and any OpenID Connect issuer.createOidcClientruns the OAuth 2.1 / OpenID Connect authorization code flow with PKCE (S256),stateandnoncekept in the session for ten minutes and one use, discovery with caching, ID token verification with WebCrypto (RS256, PS256, ES256, EdDSA; HS256 only for a confidential client that lists it) through a JWKS cache with key rotation and a refetch limit, checks ofiss,aud,azp,exp,nbf,iat,nonceandat_hash, theissresponse parameter (mix-up defense), userinfo, refresh token rotation with single-flight, and RP-initiated logout with token revocation. Every call to the provider has a timeout, no redirects and a 1 MiB cap; issuers and endpoints must be https (localhost in development).signIngives the session a new id;requireUseris a Dusk guard. Presets:google,microsoft,auth0,okta,keycloak,github,discord. AlsoverifyJwt,createJwksCache,OidcError,isOidcError. Guide: Dusk: sign-in with OAuth and OpenID Connect. -
Scalable caching: request coalescing (single flight) for the page cache and the data cache with a cap on requests per key (
cache.maxWaiters, 1,000) and on keys in flight (cache.maxInFlight, 10,000;maxInFlightof the data cache is now 10,000 too), every request getting its own response or copy of the value, failures shared and not cached, a leaving request not stopping the shared work unless every request left (499for a page request whose client left;signalofcacheData;cachedFetchfetches with the signal of the shared run), and a render timeout (cache.renderTimeoutMs, 30 s;timeoutMsof the data cache). -
Stale-while-revalidate: the background refresh of a stale page or value goes to the platform's
waitUntilwhen the request has one, a failed refresh keeps the stale copy, is reported once and backs off (cache.backoffBaseMs1 s,cache.backoffMaxMs60 s), pages answer withAgeands-maxageequal to their lifetime, and a render or run that began before a purge of one of its tags does not store its result after it. -
redisStore(client, options)(@shadow-js/dusk/stores/redis): a page cache store over a five-command Redis client interface (no Redis package), with a version tag, an entry size cap, key prefixes, tag indexes kept as sets (noSCAN), a tag purge that does not lose the index of a concurrent write, and an optional tag index expiry.CacheStoregained an optionalpurgeTag. -
runStoreContract(store)andSTORE_CONTRACT(@shadow-js/dusk/stores/contract): the scenarios every page cache store passes; the memory store, the KV adapters andredisStorerun them.
Changed
-
The page cache key is
hashKey([host, path, query, cookies])(the query key format); the KV page cache adapter stores a key or tag longer than 512 characters under its digest.createKeyValueCacheStore().tags()lists only keys whose entry still exists and carries the tag. -
--jsonon every command of theduskbin, in one documented contract (schema 1:schema,command,ok,data,warningsand, on failure,error: { code, message }with a stableDUSK_code).build,doctor,completion,--helpand--versionprint one document;devandstartprint JSON Lines (readywith the address,request,stopped). Stdout carries only JSON, logs go to stderr, nothing is asked, a refused command line and an uncaught exception are documents too, and the exit codes are unchanged. The build summary is thedataofdusk build --jsonand the doctor checks aredata.checks; the shell completion scripts offer the flag. -
File routes, server functions, API routes, middleware and request security.
-
Server rendering, streaming, prerendering, client hydration, metadata, assets and deployment adapters.
-
Server HTML applies matched route stylesheets; client navigation inherits the server URL policy.
-
Native Bun, Deno and Cloudflare WebSocket bridges share guarded upgrades, message limits and untracked callbacks.
-
WebSocket cleanup releases connection slots on failed closes and bounds heartbeat timers; Deno graceful shutdown waits for native close handshakes up to its configured deadline.
-
Node layouts declare their WebSocket and static-file middleware support; Vercel and Netlify edge targets declare explicit-event request context. The deploy guide checks its generated capability table against adapter factories.
-
serve({ compress })takeslevel,types(replaces the default compressible list) andfilter(request, response), which skips compression unless it returnstrue, so responses that reflect a secret can stay out of compression (BREACH). Options are validated when the server starts: an unknown name, a number out of range or atypesorfilterof the wrong kind throws with the option's name. zstd is negotiated by q-value after brotli and before gzip wherenode:zlibhas it at run time (dynamic responses and.zstfiles next to a static file); Node 22.12 keeps brotli and gzip. -
The Node server sends
103 Early Hintsto a browser navigation (Sec-Fetch-Mode: navigate, HTTP/1.1) to a page before the loaders run: the stylesheets, entry script and matched routes' preloads from the build, same-origin only, at most 8 (serve({ earlyHints }), on by default). The final response keeps the sameLinkheader on every adapter. Adapters declareearlyHintsin their capabilities (Node layout and Tarout), and the deploy guide's generated capability table has an Early Hints column. -
Typed streaming server functions deliver AsyncIterable items with codec preservation, consumer backpressure, bounded frames/items/duration, cancellation and safe terminal errors. The compiler emits an opt-in client stub;
useStreamowns disposal, reactive inputs and explicit reconnects.
Fixed
- Guard combinators stop after cancellation, including asynchronous alternatives; iterator callbacks and validation retain their request context.
- Streaming validation does not read inference-only schema metadata or access schemas before guards pass.
- A rolling restart of a worker pool no longer loses a connection: the primary stops handing a worker connections before the worker closes its listener (
worker.disconnect()), and the worker answers that with its soft shutdown, so no idle connection is cut either. A connection handed over in the gap used to get no answer at all. - Vite 7.0 and newer are supported next to Vite 8: the plugin picks
rollupOptionsorrolldownOptions, esbuild or Oxc, by what the installed Vite exports (src/vite/compat.ts). build.budgets.enforce: withtrue,dusk buildfails (exit code 1,DUSK_BUDGET_EXCEEDEDin--json) when a page is over its first-load budget, after the summary is printed. The default stays a warning.imageVariants()also prepares the images the code imports (import hero from "./hero.jpg"), keyed by the URL the bundler wrote (the CDN URL withassetOrigin).configureImageVariantsadds to the images it already has when called again.subsetFonts(@shadow-js/dusk/vite): cuts local font files into slices by Unicode range (FONT_SUBSETS: latin, latin-ext, vietnamese, cyrillic, greek, arabic, hebrew, devanagari, or your own) as WOFF2 files underpublic/, with theunicode-rangeof each, fordefineFonts.subset-fontis an optional peer.