@jk2908/solas 0.16.1 → 0.16.2

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +16 -75
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.2
4
+
5
+ ### Changed
6
+
7
+ - The README covers setup and the public API only. Internal architecture notes, the Node security advisory aside, and redundant examples are removed.
8
+
3
9
  ## 0.16.1
4
10
 
5
11
  ### Fixes
package/README.md CHANGED
@@ -6,7 +6,7 @@ Solas is experimental and under active development, so expect rough edges.
6
6
 
7
7
  ## Install
8
8
 
9
- Solas requires **Vite 8** (which uses Rolldown as the bundler) and **Node.js 22.23.4, 24.22.1 or newer** when using the Node runtime. Node 20 is end of life, and these are the first releases with the fix for [CVE-2026-111497](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases), a Brotli decoder flaw in Node. Solas does not decode Brotli, but your app might, including through `fetch`, which decodes `content-encoding: br` for you.
9
+ Solas requires **Vite 8** (which uses Rolldown as the bundler).
10
10
 
11
11
  ```sh
12
12
  npm install @jk2908/solas react react-dom react-server-dom-webpack vite@^8
@@ -107,22 +107,9 @@ await router.go('/posts')
107
107
  await router.go('/p/:id', { params: { id: 'post-1' } })
108
108
  ```
109
109
 
110
- That gives you:
110
+ That gives you autocomplete for known paths, required params for dynamic routes, and typed query and navigation options.
111
111
 
112
- - autocomplete for known route paths
113
- - required params for dynamic routes like `/p/:id`
114
- - rejected params for static routes that do not accept them
115
- - typed query and navigation options on `router.go(...)`
116
-
117
- If you already have a concrete path, you can pass that directly instead of using route params:
118
-
119
- ```tsx
120
- ;<Link href={`/p/${post.id}`} />
121
-
122
- await router.go(`/p/${post.id}`)
123
- ```
124
-
125
- In that form the path is already resolved, so `params` are not used. Typed `Link` and `router.go(...)` only allow `params` when you pass a route pattern like `/p/:id`.
112
+ Pass a concrete path when it is already resolved. In that form `params` are not used; typed `Link` and `router.go(...)` only accept `params` with a route pattern like `/p/:id`.
126
113
 
127
114
  If the target already contains a query string and you also pass `query`, Solas merges them. Existing query entries are kept unless you override the same key in `query`, and explicit `query` values win:
128
115
 
@@ -141,7 +128,7 @@ Use `Link` for same-origin app navigation. Prefetching is opt-in:
141
128
  <Link href="/p/:id" params={{ id: 'post-1' }}>Typed params</Link>
142
129
  ```
143
130
 
144
- `prefetch="none"` is the default. Solas does not automatically prefetch routes unless you opt in with `Link` or call `router.prefetch(...)` yourself.
131
+ `prefetch="none"` is the default. Nothing is prefetched unless you opt in with `Link` or call `router.prefetch(...)`.
145
132
 
146
133
  Use `useRouter()` inside client components for programmatic navigation, prefetching, and refreshing the current route:
147
134
 
@@ -175,9 +162,7 @@ export function Controls() {
175
162
 
176
163
  ```tsx
177
164
  const finalPath = await router.go('/posts')
178
- ```
179
165
 
180
- ```tsx
181
166
  await router.refresh()
182
167
  ```
183
168
 
@@ -192,24 +177,7 @@ await router.go('/p/:id', {
192
177
  })
193
178
  ```
194
179
 
195
- Those same generated route types can also be reused outside navigation helpers when you want route params to stay typed in page exports:
196
-
197
- ```tsx
198
- import type { Route, Solas } from '@jk2908/solas'
199
-
200
- export const metadata: Route.Metadata<Solas.Routes['/writing/:slug']> = ({ params }) => {
201
- const post = allPosts?.find(p => p.__mdsrc.slug === params?.slug)
202
-
203
- return {
204
- title: post?.title ?? 'Post not found',
205
- }
206
- }
207
-
208
- export const staticParams: Route.StaticParams<Solas.Routes['/writing/:slug']> = () =>
209
- allPosts?.map(p => ({ slug: p.__mdsrc.slug })) ?? []
210
- ```
211
-
212
- That keeps your route params aligned across links, imperative navigation, metadata, and static params.
180
+ `Route.Metadata` and `Route.StaticParams` reuse those generated types in page exports.
213
181
 
214
182
  `router.refresh()` clears the cached RSC response for the current path and fetches a fresh payload, so it is most useful for routes that render request-time data. `router.isNavigating` exposes pending client-side navigation state.
215
183
 
@@ -526,7 +494,7 @@ sitemap: {
526
494
  }
527
495
  ```
528
496
 
529
- The sitemap is written to the build output directory as `sitemap.xml` after prerendering and before precompression, and served at `/sitemap.xml`. When enabled, Solas also writes a `robots.txt` (served at `/robots.txt`) that points at the sitemap, unless a `robots.txt` is provided in the public directory.
497
+ The sitemap is written to the build output directory and served at `/sitemap.xml`. When enabled, Solas also writes a `robots.txt` (served at `/robots.txt`) that points at the sitemap, unless a `robots.txt` is provided in the public directory.
530
498
 
531
499
  ### `logger.level`
532
500
 
@@ -534,15 +502,7 @@ Use `logger.level` to control internal Solas logging.
534
502
 
535
503
  Default: `info`
536
504
 
537
- Valid values are `debug`, `info`, `warn`, `error`, and `fatal`.
538
-
539
- - `debug`: show everything
540
- - `info`: the default
541
- - `warn`: only warnings and errors
542
- - `error`: only errors
543
- - `fatal`: only fatal errors
544
-
545
- This is mainly useful when debugging framework behaviour such as routing and prerendering. It is for Solas internals, not your app's general-purpose logging, and it does not control top-level build and preview status output.
505
+ Valid values are `debug`, `info`, `warn`, `error`, and `fatal`. This controls Solas's internal logging only, not your app's logging or the build and preview status output.
546
506
 
547
507
  ```ts
548
508
  export default defineConfig({
@@ -583,8 +543,6 @@ export default defineConfig({
583
543
  })
584
544
  ```
585
545
 
586
- The adapter is passed to `solas()` rather than added to the plugins array directly, so platform wiring stays in one place (the same shape as SvelteKit's `sveltekit({ adapter: adapter() })`).
587
-
588
546
  Give the Worker a name and a compatibility date that enables `nodejs_compat`:
589
547
 
590
548
  ```ts
@@ -600,7 +558,7 @@ export default defineConfig({
600
558
  })
601
559
  ```
602
560
 
603
- > `cloudflare.config.ts` is an open beta and must be loaded with Node 22.18+ (Bun is not supported), so the Cloudflare build and deploy commands run under Node. See the [`cf` documentation](https://developers.cloudflare.com/cf/) for the format and the `cf dev` / `cf build` / `cf deploy` commands.
561
+ > `cloudflare.config.ts` is an open beta and requires Node 22.18+. The Cloudflare build and deploy commands run under Node, not Bun. See the [`cf` documentation](https://developers.cloudflare.com/cf/).
604
562
 
605
563
  #### Bindings
606
564
 
@@ -629,7 +587,7 @@ const value = await env.MY_KV.get('key')
629
587
 
630
588
  The Cloudflare Vite plugin emulates bindings in `cf dev` and `vite preview`, so there is nothing extra to configure locally.
631
589
 
632
- 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.
590
+ Bindings are unavailable during the build-time prerender; reading one warns and returns `undefined`, so read it at request time or guard it with a fallback.
633
591
 
634
592
  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:
635
593
 
@@ -639,15 +597,6 @@ For types, the plugin writes `.cloudflare/types/index.d.ts` (bindings inferred f
639
597
  }
640
598
  ```
641
599
 
642
- The adapter:
643
-
644
- - configures `@cloudflare/vite-plugin` to build the `rsc` environment as the Worker, and injects the generated entrypoint, the `ASSETS` binding, and `htmlHandling: none` / `notFoundHandling: none` into the Worker config
645
- - generates the Worker entry (a virtual module) that delegates to the typed `createWorker` helper
646
- - points Solas's prerender step at the Cloudflare Worker bundle
647
- - copies `dist/static` (full prerenders) and `dist/.solas` (runtime manifest + ppr artifacts) into the asset output under `/_solas-artifacts`
648
-
649
- `htmlHandling: none` stops the asset layer serving a prerendered document at its route path. That matters because the asset layer ignores `Accept`, so an RSC navigation (`Accept: text/x-component`) to a fully-prerendered route would otherwise receive HTML and fail to parse. Routing documents through the Worker lets Solas serve the prerender for `text/html` and render RSC otherwise, while non-HTML assets (JS, CSS, fonts, images) are still served straight from the asset layer.
650
-
651
600
  `cloudflare()` forwards its `cloudflare` option to `@cloudflare/vite-plugin`.
652
601
 
653
602
  ### Static
@@ -667,7 +616,7 @@ export default defineConfig({
667
616
  })
668
617
  ```
669
618
 
670
- `vite build` writes the site to `build/` (override with `outDir`): the client assets, each route as `<route>.html` (or `<route>/index.html` under `trailingSlash: 'always'`), its RSC payload as `<route>.rsc.txt`, plus `sitemap.xml` and `robots.txt`. Serve that directory with anything — `npx serve build`, GitHub Pages, Netlify, an S3 bucket, nginx.
619
+ `vite build` writes the site to `build/` (override with `outDir`): the client assets, each route as `<route>.html` (or `<route>/index.html` under `trailingSlash: 'always'`), its RSC payload as `<route>.rsc.txt`, plus `sitemap.xml` and `robots.txt`. Serve that directory with any static host.
671
620
 
672
621
  Client-side navigation works with no server: the browser router fetches each route's `<route>.rsc.txt` payload instead of asking a server for `text/x-component`.
673
622
 
@@ -681,7 +630,7 @@ Runtime helpers run at request time. They live under `@jk2908/solas/runtime/*` a
681
630
 
682
631
  ### Node
683
632
 
684
- `vite dev` and `vite preview` already run the built handler in Node (via `@vitejs/plugin-rsc`'s default server handler), so nothing extra is needed locally. For a production Node server, use `@jk2908/solas/runtime/node`:
633
+ `vite dev` and `vite preview` already run the built handler in Node, so nothing extra is needed locally. For a production Node server, use `@jk2908/solas/runtime/node`:
685
634
 
686
635
  ```ts
687
636
  // server.ts
@@ -699,9 +648,9 @@ await serve()
699
648
  }
700
649
  ```
701
650
 
702
- Set `NODE_ENV=production` before starting. React reads it when the built handler loads; without it the server renders development payloads, which include debug data and are noticeably larger.
651
+ Set `NODE_ENV=production` before starting, or the server renders development payloads.
703
652
 
704
- `serve()` loads the built RSC entry (`dist/rsc/index.js` by default) and forwards requests to its fetch handler, which already serves prerendered routes, client assets, and RSC/SSR responses. Options: `port` (defaults to `PORT` or `3000`), `host`, `outDir`, and `entry`. Run from the project root so the handler can resolve `dist`.
653
+ `serve()` loads the built RSC entry (`dist/rsc/index.js` by default). Options: `port` (defaults to `PORT` or `3000`), `host`, `outDir`, and `entry`. Run from the project root so the handler can resolve `dist`.
705
654
 
706
655
  Set `precompress: true` in the Solas config to emit brotli variants of client and prerendered assets at build time. `serve()` negotiates them when the client sends `accept-encoding: br` and falls back to the original file otherwise. Dynamic HTML and RSC responses are streamed per request and are not precompressed, so compress those at a proxy or CDN if you need it.
707
656
 
@@ -734,9 +683,9 @@ http2
734
683
  .listen(443)
735
684
  ```
736
685
 
737
- `serve()` itself is HTTP/1.1. TLS and HTTP/2 termination still belong at your proxy or CDN when you have one; these listeners are for deployments that terminate at Node.
686
+ `serve()` is HTTP/1.1; use these listeners when Node terminates TLS or HTTP/2 itself.
738
687
 
739
- Bun and Deno do not need the helper. Their native servers pass the handler a `Request` directly and expect a `Response`, so the Node-specific HTTP-to-Fetch bridging (streams, `duplex`, backpressure) is not needed:
688
+ Bun and Deno can call the handler directly:
740
689
 
741
690
  ```ts
742
691
  // Bun
@@ -752,13 +701,9 @@ import handler from './dist/rsc/index.js'
752
701
  Deno.serve(request => handler.fetch(request))
753
702
  ```
754
703
 
755
- The handler reads `dist` through `node:fs`, which Bun and Deno (2.x, via Node compat) provide. Run from the project root. Bun can also run `@jk2908/solas/runtime/node` as-is, since it implements `node:http`.
756
-
757
704
  ### Cloudflare runtime
758
705
 
759
- Because workers have no filesystem, the Worker reads its own output through the `ASSETS` binding. Reads use logical namespaces rather than filesystem paths: `client` maps onto asset-root URLs, and `static` (prerenders) and `artifact` (manifest, ppr) map onto the artifact prefix. During the build-time prerender pass the Worker is invoked without bindings, so the Node asset store is used and reads/writes artifacts on disk.
760
-
761
- To build a Worker manually, `createWorker` composes the entry behaviour, and `createCloudflareAssets` builds the store directly:
706
+ Workers have no filesystem, so they read Solas's output through the `ASSETS` binding. `createWorker` composes the Worker entry, and `createCloudflareAssets` builds the store:
762
707
 
763
708
  ```ts
764
709
  import { createCloudflareAssets } from '@jk2908/solas/runtime/cloudflare'
@@ -766,10 +711,6 @@ import { createCloudflareAssets } from '@jk2908/solas/runtime/cloudflare'
766
711
  const assets = createCloudflareAssets(env)
767
712
  ```
768
713
 
769
- ### Asset store
770
-
771
- 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/runtime/cloudflare` builds one around the `ASSETS` binding via `createWorker`. Build-time reads and writes use `node:fs` directly and are not abstracted.
772
-
773
714
  ## Scripts
774
715
 
775
716
  Add scripts to your app:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jk2908/solas",
3
- "version": "0.16.1",
3
+ "version": "0.16.2",
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",