Repository navigation
Warlock.js 5.38.0
Released 2026-10-25.
All 33 packages (@warlock.js/* and create-warlock) move to 5.38.0 together;
keep every family package on the same version. One change is breaking for typed
models (see section 1). The new defaults for test runtimes and the module boundary
apply to new apps or to new config only; existing apps keep their own settings.
1. Breaking changes
- Typed model
get()andset()reject unknown columns (cascade). For a model
declared with a schema type,get()andset()with a top-level column name that is
not in the schema are type errors. Add the column to the schema, use a dot path
("profile.city"), or declare the model without a schema type to keep loose access. - Nothing else in 5.38.0 is marked breaking. The changes below change behaviour
without a type error. Review them before upgrading:- A 5xx is logged as one record with a new action name (section 2); log queries or
alerts that match the old entries need updating. - A 401 or 403 on a page route renders the application's forbidden page (or the
framework page) instead of a raw JSON body (section 7). web.moduleBoundarydefaults to"warn"for existing apps (section 4).
- A 5xx is logged as one record with a new action name (section 2); log queries or
2. Request log (core, devtools, logger)
- Persistent request log. With
http.logging.requestson, each request writes one
redacted JSON record after the response is sent: id, trace id, method, URL (credential
query values masked), route, params, query, headers, session user, IP, status, duration,
phases, log entries, a query summary (count, slowest query, N+1 findings) and, for a
5xx or a thrown request, the error with its stack, phase, page file and site. - Defaults. On in development and off elsewhere; in development every request is
kept, elsewhere only errors. Records go to a daily file understorage/logs/requests/
by default.slowMs,sampleRate,include(headers, body, query, params, session,
response, stack, logs, queries, phases),maxBodyBytes(16 KB by default) and
retention: { maxDays, maxSizeMb }tune it. Outside development only allowlisted headers
are recorded, and bodies are left out unlessinclude.bodyorinclude.responseis set. - One error record per 5xx. The three earlier error entries (
request-error,
model-write-validation-error,unhandled-request-error) are replaced by one error-level
record withmodule: "http"andaction: "request.<phase>", logged once per request. A
returned503is treated as an intentional answer and is not logged. A 4xx is never logged
as an error. The per-requesthttp.logline is unchanged. - Warlock CLI and MCP.
warlock logs requests(filters--status,--route,--since,
--limit,--json) andwarlock logs request <id>read the log without booting the app.
The MCP toolrequest_logis read-only. Arequest-logskill ships with the release. - Devtools. The Requests panel reads the persisted log, so history survives a restart,
and it filters by status, route, method, time and text. It copies any request as curl,
with redacted values left as[REDACTED]. - Agents tab. The development dashboard at
/__warlockshows per-turn agent traces
(section 5). It needs no Panoptic.
3. Tests (core, create-warlock)
- Unit and integration projects.
*.unit.spec.tsand*.unit.test.tsrun in a Vitest
unitproject with no setup files and no app boot. Every other spec, and anything under
tests/, runs inintegration.guardUnitProject()fails a unit spec that imports a
connector, the connectors manager, bootstrap or a test-server helper. - Worker lifetime for new apps.
warlock add testand the starter set
lifetime: "worker"andresetDatabase: trueinsrc/config/tests.ts, and use one
database per Vitest worker.WARLOCK_TEST_LIFETIME=fileswitches back. Existing files
are never overwritten. - Per-file reset in worker lifetime. Open test HTTP servers are closed, the request
context is cleared, the route table returns to the boot snapshot, and a real in-memory
cache is flushed.setupTest({ lifetime: "file" })opts one file out of the shared
runtime. - Boot profile.
tests.profileSetup: trueorWARLOCK_TEST_PROFILE=1prints per-phase
timings, including each connector.WARLOCK_TEST_PROFILE_JSON=<path>writes them as JSON. - Fixes. The HTTP test server registers the connectors
warlock.config.tsdeclares, so a
page request no longer answers 404. A test run that booted the web connector exits cleanly
(Vite, its HMR websocket and its watchers are closed on shutdown).
4. Build (web)
- Payload budgets.
warlock buildmeasuresbuild.budgets.payloadfor each page's
translations and browser route table. A page over its limit fails the build with
mode: "error", or prints a warning withmode: "warn". - Client chunk integrity. Each build writes
client/.vite/warlock-integrity.json. The
next build fails, naming each file, when a file name now maps to different bytes. The
previousBuildoption points the check at another build, such as the deployed release. - Stable chunk names. Production chunks are named after their final bytes. Byte-identical
non-entry chunks are merged, and the manifest, preload lists, budget report and.brand
.gzfiles use the final names. - Module boundary. The client build enforces
web.moduleBoundary. Browser code may
import only browser-safe files (*.client.ts(x), schema and type files, the module's
web/folder and non-code files)."warn"is the default,"error"fails the build and
"off"disables the check. The starter sets"error". - Two tracing phases.
translationsandnamed-api-routesare reported through core's
TracingHooks.onPhase.
5. Validation and AI (seal, core, ai)
- Readable field names in validation messages. A field without a translated label is
named in words:max_dumbbell_kgreads "Max dumbbell kg". Labels underattributes.<field>
still win.validation.humanizeAttributes: falsekeeps the raw key. - Label check.
warlock doctorcheckvalidation-labels(WL4002) is off by default
and lists request-schema fields without a label whenvalidation.warnMissingLabelsis on. - Schema helpers.
humanizeAttribute(key)is exported fromseal.
ObjectValidator#partial()keeps its types..internal()marks a server-set field, and
withoutInternal()andinternalKeys()remove or list those fields. - OpenAI strict JSON Schema. An optional enum, literal, union or
$refis nullable in
toJsonSchema("openai-strict"), so a client can sendnull.draft-2020-12and
openapi-3.0nullable output is corrected too. - Tool schema lint.
ai.tool()checks its schema against OpenAI strict, Anthropic and
Google. In development it logs one warning per tool.lint: falseor per-tool options
tune it.describeToolSchema()anddescribeToolSchemas()return findings and an
estimated token cost. - Tool-use contract (opt-in). Agents can set
actWhenAsked,onToolRefusaland
backstops. Agents without them behave as before. - Eval scorers.
ai.eval.requiredToolCalled()andai.eval.changeClaimedWithoutToolCall(). - Agent traces. Each run records system prompt hash, tools offered, calls, refusals,
retries, usage and cost. Traces are on in development and off under test and in
production.tracesets this per agent. Traces are redacted and kept in an in-memory
store.diffAgentTraces()andformatAgentTrace()help compare runs. - CLI.
warlock ai toolslists per-tool token estimates and schema findings, and
warlock ai tracelists or prints agent runs from the running dev server.warlock ai
no longer marks--modelas required in its option list.
6. Core tooling (core, logger, create-warlock)
warlock upgrade --checkrehearses an upgrade on a scratch copy of the app and never
writes the real project. It reports the breaking changes that affect the app and the
outcome of each step. Child processes run one at a time with a memory cap
(--memory-mb, default 4096) and a time limit (--timeout-minutes, default 20).warlock doctorsitescheck renders each configured site's root page in
development and fails (WL5005) on any 5xx.- Capabilities.
defineCapability,provideCapability,useCapability,
hasCapabilityandlistCapabilitiesgive a module-cycle-breaking slot a typed home.
useCapabilitythrowsWL2008when nothing provides it.warlock doctorwarns about a
required capability with no provider. Ause-capabilitiesskill ships with the release. warlock inspect --section wiring(and the MCP toolinspect_wiring) lists every
auto-loaded file with its load order, then every capability slot with its provider and
consumers.- Package manager detection follows the
packageManagerfield, then the manager that
installednode_modules, then the lockfile that matchespackage.json. A stale workspace
file no longer wins.warlock updateprints the manager it chose and why. warlock agents-mdlists the skill of a package that ships a singleskills/SKILL.md
(the AI provider packages and@warlock.js/sitemap).- Logger.
LogChannel.passivemarks a channel that only observes entries. The terminal
fallback used when no log channel is configured ignores passive channels. - Loading the config keeps
@warlock.js/*imports external, so a monorepo checkout no
longer loads a second copy of core.
7. Web UX (web, create-warlock)
- Forbidden page.
forbidden.page.tsx(with an optionalforbidden.setup.ts) is the
page for a refused request. A 401 or 403 on a page route renders it with that status and
receives{ error, status }(plusdebugoutside production). The starter ships one, and
requireUser({ userType })andrequireUser({ when })now throwForbiddenError(or
UnAuthorizedErrorfor a guest) on a page route instead of answering JSON. A guest with
auth.pageAuth.loginPathis still redirected to sign in. - Framework fallback page. It shows the request id and a "Back to home" link. A 401 or
403 gets its own "You don't have access to this page." message. - Development details. The error page receives
debugoutside production, and
hydrating it no longer fails on that payload. - Multi-site dev server. The dev server accepts the hostnames in
web.sites, which
fixes the 403 "Blocked request" answer. Any host is allowed when a site isdynamic. - Route table fixes.
apiHref()anduseSubmitForm({ route })find the route table
during server render in development, including when the renderer is a separate module copy.
Production pages resolve the table once per site and viewer kind. - Server-side browser globals. In development SSR, a
*.client.*module that reads
window,document,navigator,localStorageor another browser global while imported
gets one warning that names the file and line. - Failure logs. A page failure is logged with its phase, page file, route, site and
request id, and production pages stay generic.
8. Models (cascade)
- Typed
get()andset(). Both are typed from the model's schema.get("publishedAt")
isDate | null, a column with a default is neverundefined, andset(key, value)rejects
a value of the wrong type. Dot paths stay loosely typed. A model without a schema type keeps
loose access. - Types.
ModelData<typeof schema>,ModelDataSnapshot<T>,ModelKey,ModelValueand
ModelPathare exported. - Snapshots and request schemas.
model.toDataSnapshot()returns a typed, read-only copy
without hidden columns.Model.requestSchema()drops.internal()fields, and
Model.create()still accepts them. - Assignability. A typed model is assignable to the loose
ModelandChildModelagain. - Development warning. Reading a schema column as a property (
model.title) logs one
warning per model and column that namesmodel.get("title"). Nothing is installed in
production.
9. Conventions (core, web, create-warlock)
- Unit spec file names. Use
*.unit.spec.tsor*.unit.test.tsfor specs that do not
touch the app.warlock add test, the starter and the template skills follow it. - Client-only boundary.
*.client.ts(x)is a client-only boundary. Pages, layouts and
components may import it, and it renders on the server unless wrapped in<ClientOnly>.
Server-only code may not import it. - Per-feature locale files.
src/app/<module>/utils/locales/<feature>.jsonmerges into
the module'sutils/locales.json, under the module namespace unless the file names its
own$group. A key may live in one file only. - Legacy locales are deprecated. An auto-loaded
utils/locales.tslogs a deprecation
warning that names every such file. Removal is planned for v6. - Optional barrels.
generate: { barrels: false }inwarlock.config.tsstops
generators from writingindex.tsbarrels. The default staystrue. - Starter. The starter keeps its translations in JSON files.
10. New check codes
WL9024(warning): an integration spec that imports nothing needing the app. Move it to
the unit project.WL9021(warning): a Cascade column read or written as a model property.warlock check --fix
rewrites reads tomodel.get(...)and plain assignments tomodel.set(...).WL9022(warning): server-only code imports a*.client.ts(x)module, directly or through
other files. The message names the import chain.WL9105: a translation key of a module dictionary is missing a configured locale.WL9023(opt-in): a hand-maintainedindex.tsbarrel in an app module. Enable it with
rules.WL9023in.warlock-check.json.WL4002: request-schema field without a label (validation-labels, off by default).WL5005: awarlock doctorsite page answers 5xx.WL2008: a required capability has no provider, oruseCapabilitywas called with none.
11. Upgrade notes
- Rehearse first. Run
npx warlock upgrade --check. It lists the breaking changes that
hit the app and runs typecheck,warlock doctorand the unit project on a scratch copy. - Update the family. Run
npx warlock update. It re-resolves the family packages in one
install. npm 11.6.2 or newer is required for that step. - Fix typed model calls. Add any column that
get()orset()names but the schema does
not declare, or switch to a dot path. Then runnpx warlock check --fixto rewrite property
reads and assignments toget()andset(). Review the result, because the rewrite covers
only the statically recognised cases. - Check the module boundary. Existing apps now get
web.moduleBoundary: "warn". When your
client code imports only browser-safe module files, setweb.moduleBoundary: "error"in
your web config (the starter sets it insrc/config/web.ts). The default becomes"error"in a later release. - Update log queries. Replace matches on
request-error,model-write-validation-error
andunhandled-request-errorwithaction: "request.<phase>"onmodule: "http". - Review page-route 401 and 403 handling. A refused page request now renders the forbidden
page. Check any client code or test that expects a JSON body on a page route. - Opt in to the worker test lifetime (optional). Existing apps keep their test setup. The
test-runtime-worker-lifetimeupgrade step lists where to opt in and edits nothing. Keep
clearMocksin mind: Vitest 5 clears calls a module made when it was imported, so import
such a module inside the test or setclearMocks: false. - Move legacy locales (optional). Run
npx warlock doctor --fixto convert
utils/locales.tstoutils/locales/legacy.json, or toutils/locales.jsonwhen the module
has none. The fix does nothing when a key appears in both files. - Request log in production (optional). The request log is off outside development. Set
http.logging.requests.enabledto turn it on, and setretentionto bound disk use.