@jk2908/solas 0.10.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.1
4
+
5
+ ### Fixes
6
+
7
+ - A document request is now served as HTML by default. Solas only returns the RSC payload when the client asks for `text/x-component` (and not `text/html`); a request to a page without that, such as `Accept: */*` or no `Accept` header, used to receive the RSC payload — and, for a page with no endpoint, an error.
8
+
9
+ ## 0.11.0
10
+
11
+ ### Breaking
12
+
13
+ - A route's static-params export is now `staticParams` (was `params`), matching the `Route.StaticParams` type. `Route.Prerender` became `Route.PrerenderMode` (`'full' | 'ppr'`); the `prerender` option and route export are typed `Route.PrerenderMode | false` (`false` turns prerendering off).
14
+
15
+ ### Features
16
+
17
+ - Added build guards, installed by the adapters. An adapter declares the build features it supports (`actions`, `endpoints`) and Solas warns when the graph uses one it does not. The static adapter supports none, so it warns about server actions and `+endpoint` routes.
18
+
19
+ ### Fixes
20
+
21
+ - The build-time prerender now shims `cloudflare:workers`, so an app that imports bindings at module scope no longer breaks the build. Bindings do not exist at build time, so reading one warns and returns `undefined`.
22
+
3
23
  ## 0.10.0
4
24
 
5
25
  ### Breaking
package/README.md CHANGED
@@ -205,7 +205,7 @@ export const metadata: Route.Metadata<Solas.Routes['/writing/:slug']> = ({ param
205
205
  }
206
206
  }
207
207
 
208
- export const params: Route.StaticParams<Solas.Routes['/writing/:slug']> = () =>
208
+ export const staticParams: Route.StaticParams<Solas.Routes['/writing/:slug']> = () =>
209
209
  allPosts?.map(p => ({ slug: p.__mdsrc.slug })) ?? []
210
210
  ```
211
211
 
@@ -318,10 +318,10 @@ export const prerender = false
318
318
 
319
319
  In that example, the app default is `full`, the `about` layout overrides it to `ppr`, and the page overrides it again to `false`.
320
320
 
321
- For dynamic routes, prerendering uses the params you export from the page:
321
+ For dynamic routes, prerendering uses the staticParams you export from the page:
322
322
 
323
323
  ```tsx
324
- export const params = () => [{ id: 'post-1' }, { id: 'post-2' }]
324
+ export const staticParams = () => [{ id: 'post-1' }, { id: 'post-2' }]
325
325
  export const prerender = 'full'
326
326
  ```
327
327
 
@@ -482,7 +482,7 @@ Use `sitemap` to generate a `sitemap.xml` at build time.
482
482
 
483
483
  Default: `false`
484
484
 
485
- When enabled, Solas writes a sitemap containing all routes with deterministic URLs: static routes, prerendered routes, and dynamic routes resolved via `params`. The origin for each URL comes from the resolved origin (see Config > `url`).
485
+ When enabled, Solas writes a sitemap containing all routes with deterministic URLs: static routes, prerendered routes, and dynamic routes resolved via `staticParams`. The origin for each URL comes from the resolved origin (see Config > `url`).
486
486
 
487
487
  ```ts
488
488
  export default defineConfig(({ mode }) => ({
@@ -621,6 +621,8 @@ const value = await env.MY_KV.get('key')
621
621
 
622
622
  The Cloudflare Vite plugin emulates bindings in `cf dev` and `vite preview`, so there is nothing extra to configure locally.
623
623
 
624
+ Bindings do not exist during the build-time prerender, where Solas imports the Worker in Node with `cloudflare:workers` shimmed. Reading one then warns and returns `undefined`, so guard it with a fallback or read it at request time.
625
+
624
626
  For types, the plugin writes `.cloudflare/types/index.d.ts` (bindings inferred from `cloudflare.config.ts`). Include it in `tsconfig.json` so `cloudflare:workers`'s `env` is typed:
625
627
 
626
628
  ```jsonc
@@ -709,6 +711,8 @@ export default defineConfig({
709
711
 
710
712
  Because it forces full prerendering, routes that need a server are not a fit: per-route PPR or SSR opt-ins and dynamic routes without `params()`.
711
713
 
714
+ Server actions and runtime endpoints also need a server, so the build warns when the graph contains any (a `'use server'` module or a `+endpoint` file).
715
+
712
716
  ## Runtime
713
717
 
714
718
  At request time Solas reads its output through an `Assets` interface (`exists`/`readText`/`readBuffer`/`mimeType`) injected into `createHandler`, defaulting to the Node standard library (which Bun runs natively). References are logical (`{ namespace, path }`), where the namespaces are `client` (Vite client output), `static` (full prerenders), and `artifact` (runtime manifest and ppr files). Platforms without a filesystem provide their own store; `@jk2908/solas/adapters/cloudflare/runtime` builds one around the `ASSETS` binding via `createWorker`. Build-time reads and writes use `node:fs` directly and are not abstracted.
@@ -0,0 +1,7 @@
1
+ import type { PrerenderImport } from '../../internal/postbuild.js';
2
+ /**
3
+ * Wrap the prerender import so `cloudflare:workers` resolves to a local shim
4
+ * while the server bundle loads. The bundle on disk keeps the real import; this
5
+ * only affects the Node import Solas makes to prerender.
6
+ */
7
+ export declare function withBindingsShim(next: PrerenderImport): PrerenderImport;
@@ -0,0 +1,26 @@
1
+ /** The shim module `cloudflare:workers` resolves to during the prerender. */
2
+ const WORKERS_SHIM = new URL('./workers-shim.js', import.meta.url).href;
3
+ /**
4
+ * Wrap the prerender import so `cloudflare:workers` resolves to a local shim
5
+ * while the server bundle loads. The bundle on disk keeps the real import; this
6
+ * only affects the Node import Solas makes to prerender.
7
+ */
8
+ export function withBindingsShim(next) {
9
+ return async () => {
10
+ const { registerHooks } = await import('node:module');
11
+ const hooks = registerHooks({
12
+ resolve(specifier, context, nextResolve) {
13
+ if (specifier === 'cloudflare:workers') {
14
+ return { url: WORKERS_SHIM, shortCircuit: true };
15
+ }
16
+ return nextResolve(specifier, context);
17
+ },
18
+ });
19
+ try {
20
+ return await next();
21
+ }
22
+ finally {
23
+ hooks.deregister();
24
+ }
25
+ };
26
+ }
@@ -3,8 +3,9 @@ import path from 'node:path';
3
3
  import { cloudflare as cloudflareVitePlugin } from '@cloudflare/vite-plugin';
4
4
  import * as Config from '../../config.js';
5
5
  import { writeCloudflareEntry } from '../../internal/codegen/environments.js';
6
- import { setRscEntry } from '../../internal/postbuild.js';
6
+ import { setPrerenderImport, setRscEntry } from '../../internal/postbuild.js';
7
7
  import { NAMESPACE_PREFIX } from './assets.js';
8
+ import { withBindingsShim } from './bindings.js';
8
9
  const VIRTUAL_ENTRY = 'virtual:solas-cloudflare-entry';
9
10
  /**
10
11
  * Solas dependencies that are imported at request time from the Worker
@@ -127,6 +128,9 @@ export function adapter(options = {}) {
127
128
  if (rscOutDir) {
128
129
  setRscEntry(path.resolve(cwd, rscOutDir, 'index.js'));
129
130
  }
131
+ // the Worker bundle imports `cloudflare:workers`, which only exists in
132
+ // workerd, so shim it around the Node import
133
+ setPrerenderImport(withBindingsShim);
130
134
  },
131
135
  configEnvironment(name, environment) {
132
136
  // Pre-bundle the Worker environments so workerd never has to re-optimize
@@ -0,0 +1,20 @@
1
+ export declare const env: Record<string, unknown>;
2
+ export declare const exports: Record<string, unknown>;
3
+ export declare const cache: Record<string, unknown>;
4
+ export declare const tracing: Record<string, unknown>;
5
+ export declare function waitUntil(_promise: Promise<unknown>): void;
6
+ export declare function withEnv<T>(_env: unknown, fn: () => T): T;
7
+ export declare function withExports<T>(_exports: unknown, fn: () => T): T;
8
+ export declare function withEnvAndExports<T>(_env: unknown, _exports: unknown, fn: () => T): T;
9
+ export declare class RpcStub {
10
+ }
11
+ export declare class RpcTarget {
12
+ }
13
+ export declare class WorkerEntrypoint {
14
+ }
15
+ export declare class DurableObject {
16
+ }
17
+ export declare class WorkflowStep {
18
+ }
19
+ export declare class WorkflowEntrypoint {
20
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * A stand-in for `cloudflare:workers`, loaded only while Solas imports the
3
+ * Worker bundle in Node for the build-time prerender (see `bindings.ts`). The
4
+ * real module is a workerd built-in, which Node cannot resolve.
5
+ *
6
+ * The exports mirror `@cloudflare/workers-types`' `CloudflareWorkersModule`.
7
+ * There are no bindings at build time, so reading one warns and returns
8
+ * `undefined` rather than throwing, and the classes are inert stubs so a module
9
+ * that extends one still loads. None of this runs on the deployed Worker.
10
+ */
11
+ const warned = new Set();
12
+ function binding() {
13
+ return new Proxy({}, {
14
+ get(_target, key) {
15
+ if (!warned.has(key)) {
16
+ warned.add(key);
17
+ console.warn(`[solas] "${String(key)}" was read during the build-time ` +
18
+ `prerender, where no bindings exist. It returned undefined. ` +
19
+ `Read it at request time, or guard it with a fallback.`);
20
+ }
21
+ return;
22
+ },
23
+ });
24
+ }
25
+ export const env = binding();
26
+ export const exports = binding();
27
+ export const cache = binding();
28
+ export const tracing = binding();
29
+ export function waitUntil(_promise) { }
30
+ export function withEnv(_env, fn) {
31
+ return fn();
32
+ }
33
+ export function withExports(_exports, fn) {
34
+ return fn();
35
+ }
36
+ export function withEnvAndExports(_env, _exports, fn) {
37
+ return fn();
38
+ }
39
+ export class RpcStub {
40
+ }
41
+ export class RpcTarget {
42
+ }
43
+ export class WorkerEntrypoint {
44
+ }
45
+ export class DurableObject {
46
+ }
47
+ export class WorkflowStep {
48
+ }
49
+ export class WorkflowEntrypoint {
50
+ }
@@ -14,6 +14,10 @@ export type SolasStaticOptions = {
14
14
  * then merges Solas's output into a single directory any static host can serve:
15
15
  * the client assets, the full prerenders (at their route paths) and the
16
16
  * generated `sitemap.xml` / `robots.txt`.
17
+ *
18
+ * It installs the guards with no server features supported, so the build warns
19
+ * when the graph contains server actions or `+endpoint` routes, which a static
20
+ * host cannot run.
17
21
  */
18
22
  declare function adapter(options?: SolasStaticOptions): {
19
23
  name: string;
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import * as Config from '../config.js';
4
+ import { Features, guards } from '../internal/guards.js';
4
5
  const ROOT_FILES = ['sitemap.xml', 'robots.txt'];
5
6
  /**
6
7
  * Static site adapter for Solas.
@@ -9,6 +10,10 @@ const ROOT_FILES = ['sitemap.xml', 'robots.txt'];
9
10
  * then merges Solas's output into a single directory any static host can serve:
10
11
  * the client assets, the full prerenders (at their route paths) and the
11
12
  * generated `sitemap.xml` / `robots.txt`.
13
+ *
14
+ * It installs the guards with no server features supported, so the build warns
15
+ * when the graph contains server actions or `+endpoint` routes, which a static
16
+ * host cannot run.
12
17
  */
13
18
  function adapter(options = {}) {
14
19
  const cwd = process.cwd();
@@ -39,7 +44,7 @@ function adapter(options = {}) {
39
44
  },
40
45
  },
41
46
  };
42
- const plugins = [plugin];
47
+ const plugins = [guards(Features.None), plugin];
43
48
  return { name: 'static', plugins, prerender: 'full' };
44
49
  }
45
50
  async function exists(filePath) {
package/dist/config.d.ts CHANGED
@@ -13,7 +13,8 @@ export declare const PUBLIC_DIR = "public";
13
13
  export declare const $: unique symbol;
14
14
  export declare const REQUEST_META_KEY: string;
15
15
  export declare const LOG_LEVELS: readonly ["debug", "info", "warn", "error", "fatal"];
16
- export declare const PRERENDER_MODES: readonly ["full", "ppr", false];
16
+ export declare const PRERENDER_MODES: readonly ["full", "ppr"];
17
+ export declare const PRERENDER_OPTIONS: readonly ["full", "ppr", false];
17
18
  export declare const TRAILING_SLASH_MODES: readonly ["always", "never", "ignore"];
18
19
  export declare const RUNTIME_MANIFEST = "runtime-manifest.json";
19
20
  export declare function validate(input: unknown, envUrl?: string): PluginConfig;
package/dist/config.js CHANGED
@@ -12,7 +12,8 @@ export const PUBLIC_DIR = 'public';
12
12
  export const $ = Symbol(SLUG);
13
13
  export const REQUEST_META_KEY = `__${SLUG.toUpperCase()}__`;
14
14
  export const LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'fatal'];
15
- export const PRERENDER_MODES = ['full', 'ppr', false];
15
+ export const PRERENDER_MODES = ['full', 'ppr'];
16
+ export const PRERENDER_OPTIONS = [...PRERENDER_MODES, false];
16
17
  export const TRAILING_SLASH_MODES = ['always', 'never', 'ignore'];
17
18
  export const RUNTIME_MANIFEST = 'runtime-manifest.json';
18
19
  const CONFIG_KEYS = new Set([
@@ -122,7 +123,7 @@ export function validate(input, envUrl) {
122
123
  }
123
124
  }
124
125
  if ('prerender' in input && input.prerender !== undefined) {
125
- if (!new Set(PRERENDER_MODES).has(input.prerender)) {
126
+ if (!new Set(PRERENDER_OPTIONS).has(input.prerender)) {
126
127
  errors.push("prerender must be 'full', 'ppr', or false");
127
128
  }
128
129
  }
@@ -6,7 +6,7 @@ import * as Config from '../../config.js';
6
6
  import * as RuntimeManifest from '../../manifest.js';
7
7
  import { createHttpRouter } from '../http-router/create-http-router.js';
8
8
  import { HttpRouter } from '../http-router/router.js';
9
- import { normaliseTrailingSlash } from '../http-router/utils.js';
9
+ import { isRscRequest, normaliseTrailingSlash } from '../http-router/utils.js';
10
10
  import { Metadata } from '../metadata.js';
11
11
  import { HttpException, isHttpException, toHttpException, toHttpExceptionLike, } from '../navigation/http-exception.js';
12
12
  import { isRedirect, toRedirect } from '../navigation/redirect.js';
@@ -190,7 +190,7 @@ export function createHandler(config, manifest, importMap, options = {}) {
190
190
  }
191
191
  const { stream: rscStream, status, ppr, } = await createPayload(req, manifest, importMap, config.metadata, opts.returnValue, opts.formState, opts.temporaryReferences);
192
192
  const stream = await rscStream;
193
- if (!req.headers.get('accept')?.includes('text/html')) {
193
+ if (isRscRequest(req)) {
194
194
  return new Response(stream, {
195
195
  headers: {
196
196
  'Cache-Control': 'private, no-store',
@@ -338,14 +338,13 @@ export function createHandler(config, manifest, importMap, options = {}) {
338
338
  /**
339
339
  * Serve the saved HTML for a fully prerendered route. Runs as the route
340
340
  * handler, after middleware, so middleware applies to static routes too. It
341
- * returns null for anything else (a route without a saved prerender, an RSC
342
- * navigation, or a request without an HTML accept header), so the caller
343
- * falls back to rendering.
341
+ * returns null for anything else (a route without a saved prerender or an RSC
342
+ * navigation), so the caller falls back to rendering.
344
343
  */
345
344
  async function serveFullPrerender(req) {
346
345
  if (import.meta.env.DEV)
347
346
  return null;
348
- if (!(req.headers.get('accept') ?? '').includes('text/html'))
347
+ if (isRscRequest(req))
349
348
  return null;
350
349
  if (req.headers.get(`x-${Config.SLUG}-prerender-artifact`) === '1')
351
350
  return null;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Build features a platform may not support, as flags so they combine:
3
+ * `Features.Actions | Features.Endpoints`.
4
+ */
5
+ export declare const Features: {
6
+ readonly None: 0;
7
+ readonly Actions: number;
8
+ readonly Endpoints: number;
9
+ readonly All: number;
10
+ };
11
+ export type Feature = (typeof Features)[keyof typeof Features];
12
+ /**
13
+ * Build guards, installed by an adapter: warns when the build graph uses a
14
+ * feature the adapter does not support. `supports` is a set of `Features` flags
15
+ * (`Features.Actions | Features.Endpoints`), defaulting to `Features.All`.
16
+ */
17
+ export declare function guards(supports?: Feature): {
18
+ name: string;
19
+ enforce: "pre";
20
+ transform(this: import("rolldown").TransformPluginContext, code: string, id: string): undefined;
21
+ };
@@ -0,0 +1,52 @@
1
+ import path from 'node:path';
2
+ import { Logger } from '../utils/logger.js';
3
+ const logger = new Logger();
4
+ /**
5
+ * Build features a platform may not support, as flags so they combine:
6
+ * `Features.Actions | Features.Endpoints`.
7
+ */
8
+ export const Features = {
9
+ None: 0,
10
+ Actions: 1 << 0,
11
+ Endpoints: 1 << 1,
12
+ All: (1 << 0) | (1 << 1),
13
+ };
14
+ const LABEL = {
15
+ [Features.Actions]: 'a server action',
16
+ [Features.Endpoints]: 'a runtime endpoint',
17
+ };
18
+ const SOURCE = /\.(m?[jt]sx?)$/;
19
+ const ENDPOINT = /\+endpoint\.m?[jt]sx?$/;
20
+ const USE_SERVER = /^\s*['"]use server['"]/m;
21
+ /**
22
+ * Build guards, installed by an adapter: warns when the build graph uses a
23
+ * feature the adapter does not support. `supports` is a set of `Features` flags
24
+ * (`Features.Actions | Features.Endpoints`), defaulting to `Features.All`.
25
+ */
26
+ export function guards(supports = Features.All) {
27
+ const cwd = process.cwd();
28
+ const warned = new Set();
29
+ return {
30
+ name: 'solas:guards',
31
+ enforce: 'pre',
32
+ transform(code, id) {
33
+ const file = id.split('?')[0];
34
+ if (!path.isAbsolute(file) || !file.startsWith(cwd) || warned.has(file))
35
+ return;
36
+ if (!SOURCE.test(file))
37
+ return;
38
+ let feature;
39
+ if (ENDPOINT.test(file)) {
40
+ feature = Features.Endpoints;
41
+ }
42
+ else if (USE_SERVER.test(code)) {
43
+ feature = Features.Actions;
44
+ }
45
+ if (feature === undefined || (supports & feature) !== 0)
46
+ return;
47
+ warned.add(file);
48
+ logger.warn(`[build] this build uses ${LABEL[feature]} (${path.relative(cwd, file)}), ` +
49
+ `which the adapter does not support`);
50
+ },
51
+ };
52
+ }
@@ -1,4 +1,5 @@
1
1
  import { HttpRouter } from './router.js';
2
+ import { isRscRequest } from './utils.js';
2
3
  function callEndpoint(fn, req) {
3
4
  // endpoint modules may export either a zero-arg handler or one that expects the request
4
5
  if (fn.length === 0)
@@ -82,12 +83,12 @@ export function createHttpRouter(config, manifest, importMap, rsc, serveFullPrer
82
83
  const middlewares = mergeMiddlewares(importMap[page.__id]?.middlewares, endpointEntry ? importMap[endpointEntry.__id]?.middlewares : undefined);
83
84
  router.add(page.__path, 'GET', async req => {
84
85
  const accept = req.headers.get('accept') ?? '';
85
- if (accept.includes('text/html') || accept.includes('text/x-component')) {
86
+ // a request that asks for a document or the RSC payload is rendered,
87
+ // as is any request to a page with no endpoint; anything else is the
88
+ // endpoint's own body
89
+ if (isRscRequest(req) || accept.includes('text/html') || !endpoint) {
86
90
  return (await serveFullPrerender?.(req)) ?? rsc(req);
87
91
  }
88
- if (!endpoint) {
89
- throw new Error(`Missing unified endpoint handler for ${page.__path}`);
90
- }
91
92
  return callEndpoint(endpoint, req);
92
93
  }, page.__params, middlewares);
93
94
  }
@@ -1,4 +1,12 @@
1
1
  import type { PluginConfig } from '../../types.js';
2
+ /**
3
+ * Whether a request wants the RSC payload rather than a document.
4
+ *
5
+ * The client asks for `text/x-component` on navigations. Anything else (a
6
+ * browser, or a fetch with no explicit preference) should get HTML, so a plain
7
+ * request to a page never receives the RSC payload.
8
+ */
9
+ export declare function isRscRequest(req: Request): boolean;
2
10
  export type PathPattern = {
3
11
  path: string;
4
12
  wildcardNames: Set<string>;
@@ -1,3 +1,14 @@
1
+ /**
2
+ * Whether a request wants the RSC payload rather than a document.
3
+ *
4
+ * The client asks for `text/x-component` on navigations. Anything else (a
5
+ * browser, or a fetch with no explicit preference) should get HTML, so a plain
6
+ * request to a page never receives the RSC payload.
7
+ */
8
+ export function isRscRequest(req) {
9
+ const accept = req.headers.get('accept') ?? '';
10
+ return accept.includes('text/x-component') && !accept.includes('text/html');
11
+ }
1
12
  /**
2
13
  * Escape a literal path segment so it can be used safely in a
3
14
  * path-to-regexp pattern.
@@ -1,2 +1,16 @@
1
+ import * as Prerender from './prerender.js';
1
2
  export declare function setRscEntry(entry: string): void;
3
+ type App = Parameters<typeof Prerender.run>[0];
4
+ /** Imports the server bundle `postbuild` prerenders with. */
5
+ export type PrerenderImport = () => Promise<{
6
+ default: App;
7
+ }>;
8
+ /**
9
+ * Set a wrapper for the import `postbuild` makes to prerender. It receives the
10
+ * plain import and returns one that sets up and tears down platform-specific
11
+ * module resolution around it, for example shimming a runtime module that Node
12
+ * cannot resolve (such as `cloudflare:workers`).
13
+ */
14
+ export declare function setPrerenderImport(wrap: (next: PrerenderImport) => PrerenderImport): void;
2
15
  export declare function postbuild(cwd?: string): Promise<void>;
16
+ export {};
@@ -15,6 +15,16 @@ let rscEntryOverride;
15
15
  export function setRscEntry(entry) {
16
16
  rscEntryOverride = entry;
17
17
  }
18
+ let prerenderImport;
19
+ /**
20
+ * Set a wrapper for the import `postbuild` makes to prerender. It receives the
21
+ * plain import and returns one that sets up and tears down platform-specific
22
+ * module resolution around it, for example shimming a runtime module that Node
23
+ * cannot resolve (such as `cloudflare:workers`).
24
+ */
25
+ export function setPrerenderImport(wrap) {
26
+ prerenderImport = wrap;
27
+ }
18
28
  export async function postbuild(cwd = process.cwd()) {
19
29
  const manifestPath = path.join(cwd, Config.GENERATED_DIR, 'build.json');
20
30
  let manifest;
@@ -44,7 +54,10 @@ export async function postbuild(cwd = process.cwd()) {
44
54
  logger.info('[prerender]', `prerendering ${manifest.prerenderRoutes.length} routes (concurrency: ${concurrency})...`);
45
55
  const rscEntry = rscEntryOverride ?? path.join(rscDir, 'index.js');
46
56
  rscEntryOverride = undefined;
47
- const { default: app } = await import(/* @vite-ignore */ rscEntry);
57
+ const base = () => import(/* @vite-ignore */ rscEntry);
58
+ const load = prerenderImport ? prerenderImport(base) : base;
59
+ prerenderImport = undefined;
60
+ const { default: app } = await load();
48
61
  async function enqueueWrite(task) {
49
62
  const write = task().finally(() => {
50
63
  pendingWrites.delete(write);
@@ -270,7 +270,7 @@ export async function getStaticFlag(filePath, buildContext) {
270
270
  * in the route module and calling it to get the list of parameter objects.
271
271
  */
272
272
  export async function getStaticParams(filePath, buildContext) {
273
- const params = await buildContext.exportReader.value(filePath, 'params', (v) => typeof v === 'function');
273
+ const params = await buildContext.exportReader.value(filePath, 'staticParams', (v) => typeof v === 'function');
274
274
  if (!params)
275
275
  return [];
276
276
  const resolved = await Promise.resolve().then(() => params());
package/dist/types.d.ts CHANGED
@@ -30,11 +30,11 @@ export type Adapter = {
30
30
  * Prerender mode the platform requires, which overrides the `solas()`
31
31
  * option. A static host has no server, so it needs `'full'`.
32
32
  */
33
- prerender?: Route.Prerender;
33
+ prerender?: Route.PrerenderMode | false;
34
34
  };
35
35
  type PluginConfigBase = {
36
36
  precompress?: boolean;
37
- prerender?: Route.Prerender;
37
+ prerender?: Route.PrerenderMode | false;
38
38
  metadata?: Metadata.Item;
39
39
  trailingSlash?: (typeof Config.TRAILING_SLASH_MODES)[number];
40
40
  trustedOrigins?: readonly string[];
@@ -88,7 +88,7 @@ export type Segment = {
88
88
  page?: string | null;
89
89
  };
90
90
  error?: HttpException | Error;
91
- prerender: Route.Prerender;
91
+ prerender: Route.PrerenderMode | false;
92
92
  dynamic: boolean;
93
93
  wildcard: boolean;
94
94
  };
@@ -148,7 +148,7 @@ export declare namespace Route {
148
148
  } : {
149
149
  params: ParamsOf<TRoute>;
150
150
  }) & ErrorPropsOf<TError>;
151
- export type Prerender = (typeof Config.PRERENDER_MODES)[number];
151
+ export type PrerenderMode = (typeof Config.PRERENDER_MODES)[number];
152
152
  export {};
153
153
  }
154
154
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jk2908/solas",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "A Vite + React meta-framework exploring streaming, Server Components, and partial prerendering. Designed for simplicity and lightness",
5
5
  "keywords": [
6
6
  "framework",
@@ -107,6 +107,7 @@
107
107
  "path-to-regexp": "^8.3.0"
108
108
  },
109
109
  "devDependencies": {
110
+ "@cloudflare/workers-types": "^5.20261015.1",
110
111
  "@prettier/plugin-oxc": "^0.1.3",
111
112
  "@typescript/native-preview": "^7.0.0-dev.20260224.1",
112
113
  "@vitejs/plugin-react": "^6.1.2",