Skip to content

chore: version packages - #245

Open
github-actions[bot] wants to merge 1 commit into
mainfrom
changeset-release/main
Open

chore: version packages#245
github-actions[bot] wants to merge 1 commit into
mainfrom
changeset-release/main

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.

Releases

stratal@0.1.0

Minor Changes

  • ccb3f17: Move routing onto plain Hono with lazy OpenAPI generation, add declarative response caching on Cloudflare Workers Caching, and add per-path locale detection.

    • Build the router on plain Hono with per-route validation and lazy OpenAPI generation, dropping @hono/zod-openapi and @asteasolutions/zod-to-openapi, and move the validation surface to zod/mini. A minimal no-schema worker previously shipped around 599 KB of zod and OpenAPI tooling because every app extended an OpenAPI-aware Hono app and every route registered through it; on a hello-world worker the bundle drops 944 KB to 504 KB raw, and the route-registration chunk 599 KB to 44 KB.
      • Request validation is attached per route only when a route declares params, query, or body, so schema-less routes pull in no zod at all. ctx.param(), ctx.query() and ctx.body() are unchanged.
      • This changes the validation API, the OpenAPI generation model and OpenAPIService.getSpec() — see Breaking Changes below.
    • Stamp an explicit Cache-Control header on every response, and add declarative HTTP response caching through the new stratal/response-cache entry. On a cache hit the Worker never runs, so no CPU is billed.
      • This affects every app, not only those adopting caching. Responses from routes without @Cacheable are stamped Cache-Control: private, no-store. Cloudflare Workers Caching applies RFC 9111 heuristic freshness, so a response with no Cache-Control at all is cached anyway — a 200 for two hours, a 404 for three minutes. Routes that already set their own Cache-Control are left alone. If you relied on a response having no Cache-Control header, set one explicitly.
      • Add @Cacheable({ ttl, swr, tags, vary }) for GET and HEAD routes, which emits Cache-Control: public, max-age=…[, stale-while-revalidate=…] plus Cache-Tag.
      • Add @PurgesCache({ tags, pathPrefixes, purgeEverything }) for mutations, which purges after a 2xx or 3xx. The purge is awaited, and a failure is logged with the responsible route before being rethrown as CachePurgeError, rather than leaving the cache silently inconsistent with the database.
      • Add ResponseCacheModule.forRoot({ defaults }) to supply ttl, swr and vary for every @Cacheable route. @Cacheable stays mandatory — defaults never make a route cacheable on their own. New errors: ResponseCacheConfigError, CachePurgeError, InvalidCacheTagError.
      • Interpolate {param.*}, {query.*} and {data.*} into cache tags, with a .* suffix fanning an array out to one tag per element. A rendered tag must be printable ASCII with no space, comma or double quote, and at most 1024 bytes, or it throws InvalidCacheTagError — commas and quotes are structural in the Cache-Tag header, so constrain or slugify any request-derived value before interpolating it. A {param.*} tag naming a segment the route does not declare is rejected at boot.
      • Requires "cache": { "enabled": true } in wrangler.jsonc, Wrangler 4.69.0 or newer, and a compatibility_date of 2026-07-06 or later. Without those, an app with cache decorators fails on its first request rather than silently not caching.
    • Add cache partitioning so guarded and per-tenant routes can be cached: @Cacheable({ partitionBy: [...] }) now works. Partitioned GET and HEAD reads are forwarded to a cached entrypoint, which places the resolved partitions in the part of the Workers Caching key that cannot be bypassed.
      • Export cachedEntrypoint(stratal) from stratal/workers alongside your default export, then configure ResponseCacheModule.forRoot({ gateway: { entrypoint: 'Cached' }, primers, partitions }).
      • partitionBy, partitions and primers throw at boot when gateway.entrypoint is absent — a partition an app cannot honour must fail loudly rather than cache per-caller data publicly. A guarded route is only ever cacheable with a non-empty partitionBy; @Cacheable on a guarded route without one is a boot error, since a guarded response differs per caller. Anything that is not a partitioned read runs inline exactly as before, and a partition that fails to resolve runs inline and is stamped private, no-store.
      • @PurgesCache issues its purge over RPC to the cached entrypoint when running as the gateway, because mutations run inline in the gateway, whose cache is disabled, so an inline purge would report success and invalidate nothing.
      • gateway.entrypoint is type-checked against your Worker's exports. Once you have run wrangler types, only your real, non-default export names are accepted, so a typo is a compile error rather than a runtime surprise. Without generated types it stays a plain string and is validated at runtime.
    • Add per-path locale detection: detection accepts a (path) => options resolver, alongside the new I18nModule.forRootAsync and a strategy-aware ctx.setLocale. Different areas can now use different strategies — for example a path-localized public site with a cookie-localized /admin panel — which is necessary when an area's session cookie is path-scoped.
      • The resolver must be a pure function of the path. Locale route variants are expanded at boot, once per route pattern, where no request exists, so the resolver is consulted both at boot (which routes get a variant) and per request (which detector runs), and both must agree.
      • Only routes whose path resolves to strategy: 'path' get a /:locale variant; everything else is served at its bare path and emits locale-less URLs, with no changes needed in URL builders.
      • The cookie strategy still auto-persists the locale cookie, now scoped by the resolved cookieOptions, so a per-path cookie area writes { path: '/admin' } instead of the default Path=/. Plain strategy: 'cookie' behaviour is unchanged. ctx.setLocale(locale) overrides the locale for the current request only; persistence stays the detection layer's job.
      • Adds I18nModule.forRootAsync, LocaleUrlService.isPathLocalized(path), LocalePathService.isPathLocalized(path), LocalePathService.detectionFor(path), resolveDetectionForPath(), the DetectionResolver, DetectionConfig and ResolvedDetection types, and the LOCALE_COOKIE constant.
    • Stop serving arbitrary stored content types inline from storage downloads. Downloads previously echoed an object's stored Content-Type back with Content-Disposition: inline on every disk. Because objects are served from the same origin as the application, an object stored as text/html, or as a scriptable image/svg+xml, executed against whatever session fetched it. A signed URL does not help here: it controls who may fetch an object, not what the browser does with the bytes.
      • Only application/pdf, image/png, image/jpeg, image/gif and image/webp render inline. Everything else is returned as application/octet-stream with Content-Disposition: attachment. The allowlist is the safe set rather than a blocklist of dangerous types, so a format nobody anticipated fails closed. This is a behaviour change if you relied on a non-allowlisted type rendering in the browser — it now downloads instead.
      • Every download also carries X-Content-Type-Options: nosniff, which stops the browser sniffing past the content type to render a disguised payload, and Content-Security-Policy: sandbox; default-src 'none', so even an allowlisted file handled by a viewer or decoder gets an opaque origin with no scripting.
      • Serving user-supplied content that must render is better done from a separate origin, where a compromise cannot reach the application's session.
      • Fix downloads of keys containing a space, a non-ASCII character, # or ? — most user-supplied filenames — being reported as missing, and stop a key containing a control character from producing a malformed Content-Disposition header. Non-ASCII filenames are preserved.
    • Add route visibility groups. @Controller and route options accept a groups: string[] label list; controller groups apply to every route, and route-level groups are appended. Resolved groups are exposed on each route's schema metadata as RouteSchemaMeta.groups, so the OpenAPI routeFilter can scope the document by group instead of by path string. Adds getControllerGroups() for reading a controller's declared groups.
    • Accept full schema metadata in describe() and named(), not just a description string. Pass an object to set example, examples, title, deprecated and more, all of which flow through to the generated OpenAPI document. A field's location — path, query or body — is still derived from its request slot, so there is no per-field in. Adds the SchemaMeta and SchemaMetaInput types.
    • Honour a Response returned by a short-circuiting middleware even when an outer middleware forwards control with await next() and discards the result. A middleware that returns early with ctx.redirect(...) or any other Response previously had it silently dropped, leaving the request unfinalized and throwing "Context is not finalized". This applies both to chained middlewares and to separately registered router.use chains. The Next type is widened to () => Promise<Response | void> so a forwarding middleware can return next() to propagate a downstream short-circuit without an unsafe cast; middlewares that await next() or ignore its result are unaffected.
    • Fix localized multi-segment URLs matching the wrong route when two or more locales are path-prefixed. The locale segment previously swallowed deeper paths, so a request like /fr/auth/login matched the localized index route instead of its intended route, which could produce a redirect loop on a homepage that redirects elsewhere.
    • Fix route registration failing when the router module is evaluated more than once, for example under a bundler or an SSR module runner.
    • Let errors contribute structured fields to their own log entry. ApplicationError gains an overridable reportContext() hook whose return value is merged into the logged data, so an error type can surface diagnostic detail to observability without a custom reportable() callback. The reserved keys message, name, stack and timestamp cannot be overridden, and globally registered context still takes precedence. SchemaValidationError uses this to log which field failed validation and why, where previously a failed request logged only a generic "Schema validation failed" line.
    • Stop /openapi.json failing when a route schema contains a type with no JSON Schema representation, such as z.custom, z.transform, z.instanceof, z.date, z.map or z.set. Those types now emit an empty "any" schema instead of throwing, so a single unrepresentable field no longer takes down the entire document.
    • Declare openapi3-ts as a direct dependency. It was previously resolved only transitively, so once the transitive provider was removed a clean install such as CI could not resolve it, breaking typecheck and build.
    • Remove the unused @hono/zod-openapi runtime dependency, trimming the install footprint and removing a stale transitive zod surface.
    • Stream Quarry command output to the terminal as it is produced, instead of only after the command finishes, so long-running commands such as inertia:dev show progress live. Commands run inside a worker via quarry.call() are unaffected, and their output is still returned in the command result.
    • Source process.env into Quarry's worker vars and secrets, so config passed through the environment resolves like any other binding. Local runs with a .dev.vars are unchanged, while CI and scripted runs that pass config through the environment — for example a deploy build supplying secrets as env vars — no longer fail config validation on a missing binding.
    • Stop Quarry failing with The Workers runtime failed to start on a worker that declares a Cloudflare Workflow. The CLI host cannot own a workflow entrypoint, and cannot reach one defined in another worker in local development either, so workflow bindings are now stripped from the host and logged. Trigger workflows from the worker that defines them, through an HTTP or queue handler, rather than from the CLI host.
    • Match the Workers socket contract in the Quarry CLI's Node polyfill: closing a socket now returns a promise that resolves once it is closed, and upgrading to TLS returns the upgraded socket. Both previously returned nothing, so awaiting a close in a finally block threw and masked the real result, and opportunistic TLS could not continue on the upgraded socket. Sending mail through the CLI was the common path affected.

    Breaking Changes

    • The validation API is zod/mini. The z re-export from stratal/validation is removed — it only existed to share a single zod instance with the old OpenAPI integration. Import schema builders directly from zod/mini using named imports, e.g. import { object, string, optional } from 'zod/mini', and replace classic chaining with the functional API: z.string().min(1).optional() becomes optional(string().check(minLength(1))). stratal/validation still exports cuid2 and withZodI18n, plus the new describe() and named() helpers for attaching descriptions and OpenAPI component ids, since zod/mini has no .describe() or .meta().
    • OpenAPI documents are generated lazily, on the first request to the docs endpoint, using zod v4's native JSON Schema conversion. OpenAPIService.getSpec() becomes getSpec(container) and is async — update any direct call. The routeFilter option is now a metadata predicate (route: RouteSchemaMeta) => boolean instead of (path, pathItem); filter on route.groups or route.meta rather than on the path string.
    • CacheService.put is now fire-and-forget and can no longer report failure. It schedules the KV write through waitUntil, returns a promise that resolves immediately, and logs a rejected write instead of throwing — so try { await cache.put(...) } catch { … } now sees success even when the value was never stored. KV reads are edge-cached but writes commit to KV's central store and can add hundreds of milliseconds to the request, and a cache is best-effort and eventually consistent, so this is the right default for cache writes; but any write that must not be silently lost has to move to the new CacheService.putDurable / TieredCacheService.putDurable, which await the write and throw on failure. Queue idempotency claims and failed-job records already use them, since deferring those would risk double-processing and silently lost failures. Every remaining write is now non-blocking, including the KV-backed rate limiter, which writes its counter through the same path. delete is unchanged and remains durable and awaited: invalidations such as logout or permission busting must not be deferred.
    • Every response now carries an explicit Cache-Control header. Routes without @Cacheable are stamped private, no-store. If you relied on a response having no Cache-Control at all, set one explicitly in the handler or a middleware — those are left untouched.
    • Storage downloads no longer render arbitrary content types inline. Only application/pdf, image/png, image/jpeg, image/gif and image/webp render inline; everything else downloads as an attachment. If you relied on another type rendering in the browser, serve that content from a separate origin, where a compromise cannot reach the application's session.

@stratal/framework@0.1.0

Minor Changes

  • ccb3f17: Share permissions with the client for Inertia access control, add a Workers-safe database pool factory, and fix role lookups against a renamed user model.

    • Share the current user's permissions and roles automatically once accessControl is configured, so the client can gate on them. This backs the <Can>, <Cannot>, <HasRole> and <HasNoRole> components and the useCan, useRole and useAccess hooks in @stratal/inertia, with permission strings and role names type-checked against a generated registry.
    • Add createPoolFactory(env, makePool) to @stratal/framework/database, which builds the lazy pool factory a connection's dialect hands to its dialect instance, choosing connection topology from the environment instead of hard-coding it. Write const pool = createPoolFactory(env, () => new Pool(config)), then dialect: () => new PostgresDialect({ pool }).
      • By default it returns a fresh pool per resolution, so each request owns its own pool and socket. That is mandatory on the Workers runtime, where a pool opened in one request's I/O context cannot be reused by a later request without the runtime cancelling the cross-request I/O and hanging the request. The pool is created lazily on first query, so nothing opens a socket at module scope, which the runtime forbids. In production Hyperdrive fronts these pools and multiplexes the real server connections, so they never accumulate.
      • When STRATAL_DB_SHARED_POOL is set, it instead memoizes one pool per connection, and tears that pool down exactly once no matter how many clients disconnect. @stratal/testing sets the flag automatically, because the harness runs against a direct Postgres with no Hyperdrive to multiplex — a fresh pool per resolution would accumulate until parallel test files exhausted the server's connection limit. One shared pool per connection mirrors what Hyperdrive does in production and is safe because the pool holds no per-instance state. Dev and production are unaffected.
    • Add AUTH_GATEWAY_PRIMERS, exported from @stratal/framework/auth, so guarded and per-tenant routes can use @Cacheable({ partitionBy: [...] }). The response-cache gateway resolves partitions outside the app's middleware chain, so a resolver calling ctx.user() would otherwise throw UserNotAuthenticatedError on every request; pass the constant as primers alongside gateway: { entrypoint } to run SessionVerificationMiddleware first: ResponseCacheModule.forRoot({ gateway: { entrypoint: 'Cached' }, primers: AUTH_GATEWAY_PRIMERS, partitions: { user: (ctx) => ctx.user().id } }). AUTH_GATEWAY_PRIMERS is a readonly tuple, and primers accepts it directly — no need to spread it into a new array. Partitioned reads are then forwarded to the cached entrypoint, and a partition that fails to resolve runs inline and is stamped private, no-store rather than being cached publicly. On a cache miss the session lookup is paid twice, once in the gateway and once in the app's own chain; on a hit the app never runs, so only the gateway's lookup is paid.
    • Adopt the plain-Hono router and zod/mini validation surface. Because this package re-exports the core routing and validation surface, the same migration applies — see Breaking Changes below.
    • Fix role reads and writes failing for any app whose ZenStack user model is not named exactly User. Setting a user's role, reading another user's roles, checking a permission and listing a user's permissions all threw when the model resolved to a different accessor, such as a pluralized Users model. Role lookups now resolve the user model through Better Auth regardless of ORM naming, and changing a role refreshes that user's sessions so it takes effect immediately.
    • Make disposing a shared test-harness database connection idempotent, so shutdown no longer logs "Called end on pool more than once" when multiple clients share one pool. Fresh-per-resolution pools used in dev, staging and production are unchanged.

    Breaking Changes

    • The validation API is zod/mini. The z re-export is gone from the validation surface this package re-exports. Import schema builders directly from zod/mini using named imports, and replace classic chaining with the functional API: z.string().min(1).optional() becomes optional(string().check(minLength(1))). Use describe() and named() from stratal/validation for descriptions and OpenAPI component ids, since zod/mini has no .describe() or .meta().
    • OpenAPI documents are generated lazily, on the first request to the docs endpoint. OpenAPIService.getSpec() becomes getSpec(container) and is async, and routeFilter is now a metadata predicate (route: RouteSchemaMeta) => boolean instead of (path, pathItem).

@stratal/inertia@0.1.0

Minor Changes

  • ccb3f17: Add build-time SSR exclusion and client-side access control, and fix several dev-runtime failures and oversized generated types.

    • Add build-time SSR exclusion through the stratalInertia() Vite plugin's ssrExclude option, and remove the runtime SSR opt-out. Client-only pages and their heavy dependencies were previously always bundled into the worker, because the SSR page glob pulled in every page, inflating cold start; disabling SSR at runtime skipped rendering but still shipped the code.
      • stratalInertia({ ssrExclude: ['Admin/**', 'Reports/Heavy'] }) takes page-component globs, matched against the page name, where * is a single segment and ** any number. Excluded pages are dropped from the worker bundle and rendered client-only, while the browser bundle still includes them so they hydrate normally.
      • Removes ssr.disabled and ctx.withoutSsr() — see Breaking Changes below.
    • Add client-side access control: the <Can>, <Cannot>, <HasRole> and <HasNoRole> components plus the useCan, useRole and useAccess hooks, on a new @stratal/inertia/react/access entry. They are gated on permissions the server shares automatically once accessControl is configured, and permission strings and role names are type-checked against a generated registry.
      • Also fixes two type-generator bugs that gave page props the wrong types: ctx.share() calls were not detected at all, and shared props wrapped in always(), defer(), optional(), merge() or once() were typed as the wrapper instead of the value it resolves to.
    • Recycle the dev worker when its memory reaches a threshold, fixing frequent dev-server crashes in large apps. Under sustained HMR the Workers dev isolate's heap grows until it hits the V8 limit and the worker aborts, which the browser shows as "Fetch failed". quarry inertia:dev now keeps the dev server alive, with a default threshold of 900 MB configurable through --heap-limit=<MB>. Supervision runs on macOS and Linux; elsewhere it is disabled with a warning.
    • Skip caching for Inertia pages that cannot be shared between callers, now that responses carry an explicit Cache-Control header and @Cacheable is available. A page is not cached when it carries flash data, is a partial reload, or contains a once() prop. On a cache hit the SSR render is skipped entirely, so a cached page costs no render.
    • Adopt the plain-Hono router and zod/mini validation surface. Because this package re-exports the core routing and validation surface, the same migration applies — see Breaking Changes below.
    • Render a modal route's background page client-only when that page is excluded from SSR through ssrExclude, and share that decision with full-page renders. A direct visit or refresh of such a modal route previously failed with Page not found and a 500, because the combined page was always rendered through SSR instead of honouring the exclusion.
    • Export DocumentRendererService, registered under the new INERTIA_TOKENS.DocumentRenderer token, which renders a built Page into an HTML document Response and owns the single decision between streaming SSR and a client-only shell — SSR is skipped when it is unconfigured, or when the page component was build-time excluded through ssrExclude. InertiaService and @stratal/inertia-modal both delegate to it, so that rule lives in one place; anything rendering an Inertia document outside those paths should inject the token rather than duplicate the branch.
    • Rewrite import.meta.glob page resolvers that pass a second argument, such as { eager: true } or { import: 'default' }, preserving those options. Only the bare single-argument form was matched before, so option-bearing resolvers silently shipped excluded pages into the worker bundle.
    • Strip react-dom's unused legacy synchronous server renderer from the worker SSR bundle. React's server entry pulls in both the streaming renderer that Stratal uses and a synchronous renderer it never calls, and the way they are required defeats tree-shaking, so the unused build shipped in every worker. On a minimal app the SSR chunk drops around 197 KB raw and 37 KB gzipped, taking the total worker bundle from 1,664 KB to 1,471 KB raw. SSR is streaming-only, so renderToString and renderToStaticMarkup are not available in the worker.
    • Fix ReferenceError: require is not defined returning a 500 on every SSR page under the Workers dev and SSR runtime. React 19's server entry is a CommonJS shim whose conditional require is only resolved by Vite's dependency optimizer, and because this package is excluded from that optimizer to avoid duplicate framework instances, the shim was never converted and its bare require reached the worker runtime.
    • Fix ReferenceError: require is not defined and module is not defined under the Workers dev and SSR runtime when an app uses the ORM data layer (@zenstackhq/orm) or the email renderer (@react-email/render). Both reach CommonJS sub-dependencies through packages excluded from Vite's optimizer, so they were never converted to ESM. Each is optional and is only included when it resolves from the project.
    • Fix a guest SSR render failing at app init with createPoolFactory is not a function under a linked or portal checkout, by excluding @stratal/framework from Vite's dependency optimizer alongside @stratal/inertia and stratal. The optimized database subpath lost its named exports; because the framework also re-exports the core DI tokens and Hono surface, pre-bundling it while the core is excluded could split them into two copies as well.
    • Emit translation-key page props as a type reference again, instead of inlining the whole message-key union. The reference was previously lost once a key union was reached through nested object or array expansion, so such props leaked hundreds of key literals into the generated types, while genuinely narrow literal unions still stay inlined.
    • Stop inlining the full i18n message-key union into page-prop types, which can shrink generated declaration files by an order of magnitude on apps with large key sets.
      • Nullable and optional key unions, such as InertiaTranslationKeys | null, no longer defeat detection; the null or undefined member is stripped for matching and re-attached on the emitted reference.
      • Props covering the full key set now reference MessageKeys from stratal/i18n rather than being widened to the prefix-filtered InertiaTranslationKeys.
      • A prop declared in a file that does not transitively import every message namespace resolves to a strict subset with no recoverable alias; a union that is large both as a fraction of the key space and in absolute size now collapses to the type the source declares, while small hand-picked key enums stay inlined.
      • Key detection is derived from the configured i18n prefixes and resolved inside the source tree, so the app's full key set is in scope.

    Breaking Changes

    • ssr.disabled is removed from InertiaModule.forRoot({ ssr }). Replace it with the Vite plugin's ssrExclude, which both skips SSR and drops the excluded pages from the worker bundle: stratalInertia({ ssrExclude: ['Admin/**'] }).
    • ctx.withoutSsr() and the withoutSsr context variable are removed. SSR exclusion is now build-time and declarative, so there is no per-request runtime opt-out — move the decision into ssrExclude.
    • The validation API is zod/mini. The z re-export is gone from the validation surface this package re-exports. Import schema builders directly from zod/mini using named imports, and replace classic chaining with the functional API: z.string().min(1).optional() becomes optional(string().check(minLength(1))). Use describe() and named() from stratal/validation for descriptions and OpenAPI component ids, since zod/mini has no .describe() or .meta().
    • OpenAPI documents are generated lazily, on the first request to the docs endpoint. OpenAPIService.getSpec() becomes getSpec(container) and is async, and routeFilter is now a metadata predicate (route: RouteSchemaMeta) => boolean instead of (path, pathItem).

@stratal/testing@0.1.0

Minor Changes

  • ccb3f17: Give each test file its own database, drain deferred work before a test finishes, and supply the cache and gateway bindings the runtime never populates.

    • Give every test file its own database, cloned from the migrated template and retargeted onto the Hyperdrive binding, replacing per-compile template clones. Within a file, tests reset state through truncateDb or the reset engine.
      • Per-file isolation is deliberate: the Workers test pool isolates storage per file and can run a worker's files concurrently, so any database shared across files corrupts under CI latency. Per-file matches the pool's own model and makes cross-file contamination impossible by construction.
      • Clones are serialized by a Postgres advisory lock, so only one clone runs at a time and contention stays bounded by the number of concurrent files. A global-setup sweep reclaims leaked databases on the next run.
      • createTestDatabaseGlobalSetup accepts a one-time prepare hook to bake expensive baseline state, such as seed data or a default tenant schema, into the template once, so every file's database inherits it through the clone instead of rebuilding it per test.
      • truncateDb(name?, opts?) accepts a ResetOptions preserve-list; the migration tables matching _prisma% are always preserved.
      • There is now a single isolation model, which removes the isolation toggle and the old clone and drop helpers — see Breaking Changes below.
    • Supply the ctx.cache binding so cache-decorated routes are testable with no configuration. Neither Miniflare nor workerd ever populates it, so without this a single @Cacheable or @PurgesCache route would fail an app's entire suite on the first request. Test.createTestingModule() installs a stub by default: @Cacheable routes return real Cache-Control and Cache-Tag headers, and purges succeed, recording each PurgeSpec in call order on module.cache.purges. Pass cache: false to opt back into the unconfigured runtime, for example to test the configuration boot guard.
    • Supply a ctx.exports stub by default so adopting the response-cache gateway does not break existing suites. Assert forwarded requests and their resolved partitions through module.gateway.loopbacks. The stub answers to any export name, because it cannot know yours, so a passing suite is not what proves your configured entrypoint is correct — the type check against your Worker's exports is. A wrong name otherwise surfaces on the first request after deploy, as a ResponseCacheConfigError naming the exports it can actually see.
    • Drain work a request defers through ctx.waitUntil before fetch() resolves, mirroring the Workers runtime, which keeps a request alive until its deferred promises settle. A non-blocking listener's deferred side-effect, such as a database write, previously stayed in flight past the response and could still be running against a shared resource at the next request or at teardown, where disposing that resource hung the suite past the hook timeout. Deferred work now completes within the request that triggered it, and waitUntil semantics are otherwise unchanged.
    • Drain deferred work in close() before tearing the app down. fetch() already drained per call, but the non-HTTP helpers for websockets, SSE and Quarry share the same queue, so a suite using only those could reach teardown with database writes still in flight and race the connection pool's disposal. Shutdown is now deterministic regardless of which helper enqueued the work.
    • Default database-isolation projects to a 30 second hook timeout. Enabling database turns on real file parallelism, and each file's setup clones the template into its own database — a CREATE DATABASE … TEMPLATE serialized across concurrent files by a Postgres advisory lock — on top of whatever the app provisions in its own beforeAll, such as a tenant or seed data. Under a full worker slot that routinely exceeds Vitest's 10 second default and fails with "Hook timed out in 10000ms" even though the work would have completed. This is a floor, not a ceiling: a project with heavier setup can still raise hookTimeout for its own suites.
    • Share one database pool per connection in the test harness, and tear it down exactly once. The harness runs against a direct Postgres with no Hyperdrive to multiplex connections, so a fresh pool per resolution would accumulate until parallel files exhausted the server's connection limit with "sorry, too many clients already". Disposing a connection no longer logs "Called end on pool more than once". Consuming apps need no test-config changes.
    • Fix chunked uploads to the fake storage service failing when the body is a single-use stream, which is the shape a chunked upload delivers. The body was read twice — once to size it and once to store it — throwing ReadableStream is disturbed; it is now consumed exactly once.

    Breaking Changes

    • There is now a single database isolation model. The shared and database isolation toggle is gone, along with the isolation option on both stratalTest({ database }) and createTestDatabaseGlobalSetup. Pass stratalTest({ database: {} }) to enable isolation and delete any isolation: option; globalSetup no longer takes an isolation mode.
    • createTestDatabaseGlobalSetup now requires schema. Add it if you were relying on the previous default.
    • The clone and drop helpers createDatabaseFromTemplate, deriveDbName and dropDatabase are removed. Per-file databases are created and reclaimed automatically, so remove any manual calls; use truncateDb to reset state between tests within a file.

@stratal/feature-flags@0.1.0

Patch Changes

  • ccb3f17: Released alongside the rest of the packages; nothing changed in this one.

    • No functional or API change ships here. Every Stratal package is versioned as one fixed group, so @stratal/feature-flags is republished at the same version as the packages it builds on rather than being left behind at the previous one. Its peer ranges on stratal and @stratal/inertia are open-ended, so an existing install keeps resolving — upgrade only to keep one aligned set of versions across the framework.

@stratal/inertia-modal@0.1.0

Patch Changes

  • ccb3f17: Render a modal route's background page client-only when that page is excluded from SSR.

    • Render a modal route's background page client-only when it is excluded from SSR at build time through stratalInertia({ ssrExclude }). A direct visit or refresh of such a modal route previously failed with Page not found and a 500, because the combined page was always rendered through SSR instead of honouring the same exclusion as a full-page render. The excluded page now renders client-only for the browser bundle to hydrate, so a modal route works under both SSR and client-side rendering.

@adesege adesege left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Details
Benchmark suite Current: 4a953f1 Previous: ccb3f17 Ratio
test/benchmarks/request-response.bench.ts > Request/Response > simple GET - 200 36969.844135137144 ops/sec (±1.32%) 44355.827899388205 ops/sec (±1.52%) 1.20
test/benchmarks/request-response.bench.ts > Request/Response > GET with route params - 200 45996.258581652866 ops/sec (±0.62%) 49077.55133303189 ops/sec (±1.07%) 1.07
test/benchmarks/request-response.bench.ts > Request/Response > POST with JSON body - 201 16285.125619036411 ops/sec (±1.18%) 24127.579263272528 ops/sec (±1.28%) 1.48
test/benchmarks/request-response.bench.ts > Request/Response > POST invalid body - validation error 4010.3241256510996 ops/sec (±3.11%) 4332.798367697519 ops/sec (±3.08%) 1.08
test/benchmarks/request-response.bench.ts > Request/Response > GET unknown route - 404 5999.544886524008 ops/sec (±1.44%) 6877.491973422911 ops/sec (±1.38%) 1.15
src/__benchmarks__/application.bench.ts > Application - Bootstrap > constructor only 369828.7943581282 ops/sec (±0.64%) 367439.1967778971 ops/sec (±2.58%) 0.99
src/__benchmarks__/application.bench.ts > Application - Bootstrap > full initialize() 36418.94472465844 ops/sec (±1.20%) 40916.51636711715 ops/sec (±2.82%) 1.12
src/__benchmarks__/application.bench.ts > Application - Service Resolution > resolve service after bootstrap 42820.22578676439 ops/sec (±1.37%) 46221.77899438597 ops/sec (±1.51%) 1.08
src/__benchmarks__/application.bench.ts > Application - Multi-Controller Bootstrap > initialize with 5 controllers (8 routes) 42603.526163578375 ops/sec (±0.72%) 47297.584538015675 ops/sec (±0.94%) 1.11
src/__benchmarks__/application.bench.ts > Application - Multi-Controller Bootstrap > resolve service after multi-controller bootstrap 41885.811932707955 ops/sec (±0.65%) 46712.398512126 ops/sec (±0.86%) 1.12
src/di/__benchmarks__/container.bench.ts > Container - Registration > register class provider 4271033.145793519 ops/sec (±1.68%) 3659523.004609583 ops/sec (±0.26%) 0.86
src/di/__benchmarks__/container.bench.ts > Container - Registration > registerSingleton 4026860.8724799873 ops/sec (±2.13%) 3674735.919157303 ops/sec (±0.23%) 0.91
src/di/__benchmarks__/container.bench.ts > Container - Registration > registerValue 4580004.16800319 ops/sec (±2.24%) 3643329.0527346027 ops/sec (±0.97%) 0.80
src/di/__benchmarks__/container.bench.ts > Container - Registration > registerFactory 4670144.692362833 ops/sec (±0.74%) 3731349.291036125 ops/sec (±0.79%) 0.80
src/di/__benchmarks__/container.bench.ts > Container - Resolution > resolve class token 1407599.000603626 ops/sec (±0.42%) 1231183.1036989056 ops/sec (±0.71%) 0.87
src/di/__benchmarks__/container.bench.ts > Container - Resolution > resolve symbol token 1424788.6749439533 ops/sec (±0.41%) 1316018.4734202325 ops/sec (±0.72%) 0.92
src/di/__benchmarks__/container.bench.ts > Container - Resolution > resolve value token 1933069.095325256 ops/sec (±2.28%) 1783899.5774658315 ops/sec (±0.30%) 0.92
src/di/__benchmarks__/container.bench.ts > Container - Resolution > resolve singleton token 1352330.2527890466 ops/sec (±0.43%) 1315930.5814242992 ops/sec (±0.55%) 0.97
src/di/__benchmarks__/container.bench.ts > Container - Resolution > isRegistered check 2106508.9635955375 ops/sec (±0.20%) 1806360.778904609 ops/sec (±0.19%) 0.86
src/di/__benchmarks__/container.bench.ts > Container - Conditional Binding > when().use().give().otherwise() 2462564.419034541 ops/sec (±3.00%) 1989351.3462046375 ops/sec (±2.25%) 0.81
src/di/__benchmarks__/container.bench.ts > Container - Conditional Binding > when() with cached predicate 2439603.0290414556 ops/sec (±0.63%) 2077218.3049918814 ops/sec (±0.64%) 0.85
src/module/__benchmarks__/module-registry.bench.ts > ModuleRegistry - Registration > register single module 1353256.167691235 ops/sec (±0.62%) 1318623.2747570868 ops/sec (±0.43%) 0.97
src/module/__benchmarks__/module-registry.bench.ts > ModuleRegistry - Registration > register 3-level module tree 679841.4819609317 ops/sec (±0.44%) 674368.8967327109 ops/sec (±0.61%) 0.99
src/module/__benchmarks__/module-registry.bench.ts > ModuleRegistry - Registration > register dynamic module (forRoot) 1132211.8075242015 ops/sec (±0.39%) 1033574.8692689876 ops/sec (±0.46%) 0.91
src/module/__benchmarks__/module-registry.bench.ts > ModuleRegistry - Initialization > initialize with lifecycle hooks 820519.6717920463 ops/sec (±0.57%) 861264.3797109328 ops/sec (±0.67%) 1.05
src/module/__benchmarks__/module-registry.bench.ts > ModuleRegistry - Collection > getAllControllers 603021.1461219145 ops/sec (±0.47%) 601067.2979531069 ops/sec (±0.70%) 1.00
src/module/__benchmarks__/module-registry.bench.ts > ModuleRegistry - Collection > getAllConsumers 599319.9916095391 ops/sec (±0.44%) 598005.5897686194 ops/sec (±2.06%) 1.00
src/module/__benchmarks__/module-registry.bench.ts > ModuleRegistry - Collection > getAllJobs 602324.2990362238 ops/sec (±0.40%) 606220.5693193878 ops/sec (±0.44%) 1.01
src/router/__benchmarks__/route-registration.bench.ts > RouteRegistration - Configure > register controller with 5 OpenAPI routes 16893.46751790366 ops/sec (±6.80%) 20659.201604495036 ops/sec (±7.81%) 1.22
src/router/__benchmarks__/route-registration.bench.ts > RouteRegistration - Configure > register single-route controller 84336.03405772212 ops/sec (±8.21%) 87549.06480088776 ops/sec (±9.59%) 1.04
src/router/__benchmarks__/route-registration.bench.ts > RouteRegistration - Configure > register multiple controllers 17238.784700155928 ops/sec (±6.85%) 20055.348642388202 ops/sec (±7.48%) 1.16
src/router/__benchmarks__/route-registration.bench.ts > Route Sorting > sort 10 routes by specificity 334083.91447458765 ops/sec (±1.72%) 348297.84898624144 ops/sec (±1.02%) 1.04
src/router/__benchmarks__/route-registration.bench.ts > Route Sorting > sort 50 routes by specificity 63716.075009950684 ops/sec (±0.38%) 66342.982298651 ops/sec (±0.49%) 1.04
src/router/__benchmarks__/route-registration.bench.ts > Route Sorting > sort 100 routes by specificity 30807.01343620418 ops/sec (±0.38%) 32949.193140155745 ops/sec (±0.54%) 1.07
src/router/__benchmarks__/route-registration.bench.ts > Param Extraction > extractParamNames - static path 12304712.0558419 ops/sec (±0.13%) 12503091.774915366 ops/sec (±0.09%) 1.02
src/router/__benchmarks__/route-registration.bench.ts > Param Extraction > extractParamNames - single param 1801191.7982674923 ops/sec (±0.44%) 2209847.5226742206 ops/sec (±1.46%) 1.23
src/router/__benchmarks__/route-registration.bench.ts > Param Extraction > extractParamNames - multiple params 1295222.9353269946 ops/sec (±0.65%) 1332989.981337998 ops/sec (±0.60%) 1.03

This comment was automatically generated by workflow using github-action-benchmark.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant