This guide covers application changes when moving from vextjs v1.x to the stable 2.0.0 release.
- Replace every
app.monsqlizeaccess withapp.dband remove assumptions thatapp.dbis a reduced facade. - Audit every Model lookup for its exact registered key.
- If the application relied on implicit global rate limiting, add
rateLimit: { enabled: true, ... }tosrc/config/default.ts. - Update assertions for path-parameter validation failures from HTTP 422 to HTTP 400; body/query/header/cookie validation remains HTTP 422.
- Run typecheck, build, runtime, OpenAPI, SEO/sitemap, and no-hydration probes in a real consumer project before deploying.
For the published release, the framework upgrade command is:
npm install vextjs@2.0.0When validating a source change before a later publication, install the one
generated .tgz candidate in the consumer instead of using a workspace/source
link.
v2 removes app.monsqlize. When config.database exists, app.db is the
same raw MonSQLize instance created and connected by Vext, with a read-only
client getter and narrow soft-delete result compatibility installed on that
same object. It is not a facade or Proxy.
// v1
await app.monsqlize.withTransaction(async (transaction) => {
// ...
});
// v2
await app.db.withTransaction(async (transaction) => {
// ...
});Collections, Models, transactions, pools, scopedModel(), sync, events,
diagnostics, and management APIs now share the one app.db entry point. Vext
still owns connection cleanup; do not add a second application onClose hook
that closes app.db again.
Database and pool scope no longer imply a registry-key transformation:
// models/billing/invoice.ts registers BillingInvoice
app.db.use("billing").model("BillingInvoice");
// models/cn/billing/order.ts registers CnBillingOrder
app.db.pool("cn").use("billing").model("CnBillingOrder");use() and pool() select the database/pool scope only. They do not prepend a
scope name, search a short name, or fall back to a different key. At the root,
an explicit collection or name becomes the registry key; otherwise Vext
uses the PascalCase filename. At directory depth 1 or 2, Vext uses the
PascalCase path key. A short key works only when the Model explicitly declares
that key alias.
The default is now disabled. Vext installs no rate-limit middleware, headers, or HTTP 429 behavior unless configuration explicitly contains:
export default {
rateLimit: {
enabled: true,
max: 100,
window: 60,
},
};app.setRateLimiter() still replaces the limiter implementation but does not
enable the global middleware. Once enabled, route-level
override: { rateLimit: false } and route-level limit objects keep their v1
semantics. Direct use of the exported createRateLimitMiddleware() factory is
also unchanged.
An invalid or missing path parameter now produces HTTP 400, matching malformed
URL input. Body, query, header, and cookie validation failures remain HTTP 422.
The public VextValidationError status, runtime error handler, and generated
OpenAPI responses use the same source-specific rule.
frontend.seoconfigures framework-level metadata, canonical URLs, robots, build/runtime sitemaps, provider-generated entries, explicit deployment origins, and per-page route/render overrides.- A rendered route can set
hydration: "none"to return server HTML without a client entry, page payload, or React runtime. This is route-level pure HTML, not selective hydration, Islands, or PPR. - OpenAPI Docs now uses the same Vext mark geometry, favicon, teal/cyan light/dark tokens, and green/amber mark accents as the project frontend.
- The Hello World and MongoDB CRUD examples are executable fixtures with install, typecheck, build, runtime, and OpenAPI assertions.
For a three-argument route declaration, the route-options argument and its
frontend value must be inline object literal values. The build index does
not execute imported variables or helper functions. A dynamic reference that
could previously be omitted from the build manifest now fails with the route
file, method, and path instead of silently applying frontend defaults. Keep
request-dependent SEO in res.render(..., { seo }).
Only newly created TypeScript projects receive the clearer type layout:
src/types/
├── shared/ # browser/server-safe application contracts (full-stack)
├── frontend/ # browser-facing declarations (full-stack)
└── generated/ # owned by vext typegen
TypeScript API-only projects create only src/types/generated/; JavaScript
projects create no src/types/. The TypeScript templates declare
@types/node, so Node globals are available without relying on transitive
dependency hoisting. Existing project directories are not moved, renamed, or
deleted, and vext typegen continues to own only src/types/generated/.
Runtime configuration for both frontend and backend remains in src/config/.
This guide describes the migration required by stable schema-dsl@3.0.0 and
monsqlize@3.1.0 when upgrading to vextjs@1.0.0.
The source line later moved to
schema-dsl@3.0.4andmonsqlize@3.3.0. That backward-compatible dependency update does not alter the original v1 migration order below. It adds the optionaldatabase.monsqlizeOptionscontrolled escape hatch; existing first-classdatabase.*configuration remains valid.
The release order is fixed because vextjs consumes both packages directly:
schema-dsl@3.0.0monsqlize@3.1.0, pinned toschema-dsl@3.0.0vextjs@1.0.0, pinned to both stable versions
Install the GA upstream packages before upgrading the framework:
npm install schema-dsl@3.0.0 monsqlize@3.1.0 vextjs@1.0.0Importing vextjs or schema-dsl no longer installs
String.prototype.description. Replace the legacy global chain:
const name = "string!".description("User name");with the explicit, side-effect-free builder:
import { schemaAdapter } from "vextjs";
const name = schemaAdapter.compileField("string!").description("User name");Vext does not expose isRequired() or isOptional(). Complete object schemas
already expose required fields through standard JSON Schema required[], so a
second field-state API would duplicate the public contract.
The ? suffix means that a property may be absent. It does not allow null and
does not produce OpenAPI nullable: true:
const optionalOnly = { nickname: "string?" };Declare null explicitly when it is part of the value domain:
const nullableDsl = { nickname: "types:string|null" };
const nullableJsonSchema = { nickname: { type: ["string", "null"] } };Vext projects these lossless two-branch unions to OpenAPI 3.0 as the concrete
type plus nullable: true. Complex unions that cannot be represented without
losing meaning fail with a clear conversion error.
The v1 frontend runtime continues to use the existing Route plus
res.render() model. Streaming SSR is opt-in through
frontend.render.streaming: "auto"; the default remains "buffered".
Same-route navigation, page envelopes, static/revalidate freshness, and local
Image / defineFont media output are additive public capabilities.
No migration to file routes, loader/action routes, React Server Components, Server Functions, Server Actions, PPR, or a Webpack/Vite/Rollup/Rolldown plugin ecosystem is required. Existing document navigation remains the fallback when a page-envelope response cannot be used.
Successful schema-dsl validation returns data, which contains the value
after coercion, defaults, and other configured normalization. Vext continues to
store that value for req.valid(source) and validation-success hooks; using the
raw request value would silently discard conversions such as query strings to
numbers.
At the upstream boundary Vext now reads only canonical
errors[].path and errors[].message. It maps them to Vext's existing public
HTTP shape { field, message }, so application error responses do not change.
Deprecated upstream aliases such as field, type, and expected are not
required by Vext.
npm run release:preflight is the non-publishing source gate.
npm run release:preflight:final requires a stable v1 package identity, exact
stable upstream dependencies, a clean main worktree, changelogs/v1.0.0.md,
and identity-bound external consumer evidence at
release/v1-external-validation.json.