@jk2908/solas 0.8.0-canary.1791495022678 → 0.8.0

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,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.8.0
4
4
 
5
5
  ### Features
6
6
 
@@ -1,4 +1,4 @@
1
- import type { Assets } from '../../internal/runtimes/assets.js';
1
+ import type { AssetNamespace, Assets } from '../../internal/runtimes/assets.js';
2
2
  /**
3
3
  * The subset of the Cloudflare `assets` Fetcher binding that Solas needs.
4
4
  */
@@ -13,17 +13,20 @@ export type CloudflareEnv = {
13
13
  [key: string]: unknown;
14
14
  };
15
15
  /**
16
- * Path prefix the adapter copies `dist/.solas` prerender artifacts under in the
17
- * asset output.
16
+ * Path prefix the adapter copies `dist/.solas` artifacts under in the asset
17
+ * output.
18
18
  */
19
19
  export declare const ARTIFACT_PREFIX = "_solas-artifacts";
20
20
  /**
21
- * Prerendered documents live under the artifact prefix rather than the asset
22
- * root. If they sat at their route paths the asset layer would serve them for
23
- * every request to that path, so an RSC navigation (`Accept:
24
- * text/x-component`) would receive HTML instead of reaching the Worker.
21
+ * Where each namespace lives in the asset output, keyed by the shared
22
+ * `AssetNamespace`. `client` files sit at the asset root; `static` (prerenders)
23
+ * and `artifact` (manifest, ppr) live under the artifact prefix.
24
+ *
25
+ * Prerenders must not sit at their route paths: the asset layer ignores
26
+ * `Accept`, so it would answer an RSC navigation (`Accept: text/x-component`)
27
+ * with HTML instead of letting the Worker render it.
25
28
  */
26
- export declare const PRERENDER_PREFIX = "_solas-artifacts/static";
29
+ export declare const NAMESPACE_PREFIX: Record<AssetNamespace, string>;
27
30
  /**
28
31
  * Create a Solas asset store backed by the Cloudflare assets binding.
29
32
  *
@@ -1,19 +1,26 @@
1
1
  import { getMimeTypeFromPath } from '../../internal/runtimes/mime.js';
2
2
  /**
3
- * Path prefix the adapter copies `dist/.solas` prerender artifacts under in the
4
- * asset output.
3
+ * Path prefix the adapter copies `dist/.solas` artifacts under in the asset
4
+ * output.
5
5
  */
6
6
  export const ARTIFACT_PREFIX = '_solas-artifacts';
7
- /**
8
- * Prerendered documents live under the artifact prefix rather than the asset
9
- * root. If they sat at their route paths the asset layer would serve them for
10
- * every request to that path, so an RSC navigation (`Accept:
11
- * text/x-component`) would receive HTML instead of reaching the Worker.
12
- */
13
- export const PRERENDER_PREFIX = `${ARTIFACT_PREFIX}/static`;
14
7
  // The assets binding matches on pathname only, so this is only used to satisfy
15
8
  // the URL constructor
16
9
  const ASSET_ORIGIN = 'https://solas-assets.invalid';
10
+ /**
11
+ * Where each namespace lives in the asset output, keyed by the shared
12
+ * `AssetNamespace`. `client` files sit at the asset root; `static` (prerenders)
13
+ * and `artifact` (manifest, ppr) live under the artifact prefix.
14
+ *
15
+ * Prerenders must not sit at their route paths: the asset layer ignores
16
+ * `Accept`, so it would answer an RSC navigation (`Accept: text/x-component`)
17
+ * with HTML instead of letting the Worker render it.
18
+ */
19
+ export const NAMESPACE_PREFIX = {
20
+ client: '',
21
+ static: `${ARTIFACT_PREFIX}/static`,
22
+ artifact: ARTIFACT_PREFIX,
23
+ };
17
24
  /**
18
25
  * Create a Solas asset store backed by the Cloudflare assets binding.
19
26
  *
@@ -26,47 +33,36 @@ export function createCloudflareAssets(env) {
26
33
  if (!assets || typeof assets.fetch !== 'function') {
27
34
  throw new Error('[solas] createCloudflareAssets requires an `ASSETS` binding. Add an assets binding to your Worker.');
28
35
  }
29
- /**
30
- * Where each namespace lives in the asset output: `client` files sit at the
31
- * asset root, prerenders and artifacts under the artifact prefix.
32
- */
33
- function toAssetPath(asset) {
34
- switch (asset.namespace) {
35
- case 'artifact':
36
- return `${ARTIFACT_PREFIX}/${asset.path}`;
37
- case 'static':
38
- return `${PRERENDER_PREFIX}/${asset.path}`;
39
- default:
40
- return asset.path;
41
- }
42
- }
36
+ // narrowed here, and `const` keeps that type inside the closures below
37
+ const binding = assets;
43
38
  function toUrl(asset) {
44
- return new URL(`/${toAssetPath(asset)}`, ASSET_ORIGIN).toString();
39
+ const prefix = NAMESPACE_PREFIX[asset.namespace];
40
+ const path = prefix ? `${prefix}/${asset.path}` : asset.path;
41
+ return new URL(`/${path}`, ASSET_ORIGIN).toString();
45
42
  }
46
43
  async function read(asset) {
47
- const res = await assets.fetch(toUrl(asset), {
44
+ const res = await binding.fetch(toUrl(asset), {
48
45
  // asset responses should be passed through verbatim
49
46
  headers: { 'accept-encoding': 'identity' },
50
47
  });
51
48
  return res.ok ? res : null;
52
49
  }
50
+ async function readOrThrow(asset) {
51
+ const res = await read(asset);
52
+ if (!res) {
53
+ throw new Error(`[solas] asset not found: ${asset.namespace}/${asset.path}`);
54
+ }
55
+ return res;
56
+ }
53
57
  return {
54
58
  async exists(asset) {
55
59
  return (await read(asset)) !== null;
56
60
  },
57
61
  async readText(asset) {
58
- const res = await read(asset);
59
- if (!res) {
60
- throw new Error(`[solas] asset not found: ${asset.namespace}/${asset.path}`);
61
- }
62
- return res.text();
62
+ return (await readOrThrow(asset)).text();
63
63
  },
64
64
  async readBuffer(asset) {
65
- const res = await read(asset);
66
- if (!res) {
67
- throw new Error(`[solas] asset not found: ${asset.namespace}/${asset.path}`);
68
- }
69
- return res.arrayBuffer();
65
+ return (await readOrThrow(asset)).arrayBuffer();
70
66
  },
71
67
  mimeType(filePath) {
72
68
  return getMimeTypeFromPath(filePath);
@@ -4,7 +4,7 @@ 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
6
  import { setRscEntry } from '../../internal/postbuild.js';
7
- import { ARTIFACT_PREFIX, PRERENDER_PREFIX } from './runtime.js';
7
+ import { NAMESPACE_PREFIX } from './runtime.js';
8
8
  const VIRTUAL_ENTRY = 'virtual:solas-cloudflare-entry';
9
9
  /**
10
10
  * Solas dependencies that are imported at request time from the Worker
@@ -56,13 +56,13 @@ async function copyOutput(cwd) {
56
56
  const assetDirs = await findAssetDirs(path.join(cwd, '.cloudflare', 'output'));
57
57
  for (const assetDir of assetDirs) {
58
58
  if (await pathExists(artifactSource)) {
59
- await fs.cp(artifactSource, path.join(assetDir, ARTIFACT_PREFIX), {
59
+ await fs.cp(artifactSource, path.join(assetDir, NAMESPACE_PREFIX.artifact), {
60
60
  recursive: true,
61
61
  force: true,
62
62
  });
63
63
  }
64
64
  if (await pathExists(staticSource)) {
65
- await fs.cp(staticSource, path.join(assetDir, PRERENDER_PREFIX), {
65
+ await fs.cp(staticSource, path.join(assetDir, NAMESPACE_PREFIX.static), {
66
66
  recursive: true,
67
67
  force: true,
68
68
  });
@@ -1,4 +1,4 @@
1
- import { createHandler, loadManifest } from '../../internal/env/rsc.js';
1
+ import { createRuntimeHandler } from '../../internal/env/rsc.js';
2
2
  import { nodeAssets } from '../../internal/runtimes/assets.js';
3
3
  import { createCloudflareAssets } from './runtime.js';
4
4
  /**
@@ -13,11 +13,7 @@ export function createWorker(options) {
13
13
  const handlers = new WeakMap();
14
14
  let nodeHandler;
15
15
  async function build(assets) {
16
- const runtimeManifest = await loadManifest(assets);
17
- return createHandler(options.config, options.manifest, options.importMap, {
18
- assets,
19
- runtimeManifest,
20
- });
16
+ return createRuntimeHandler(options.config, options.manifest, options.importMap, assets);
21
17
  }
22
18
  function getHandler(env) {
23
19
  if (!env) {
@@ -7,15 +7,13 @@ export function writeRSCEntry(_config) {
7
7
  return source `
8
8
  ${AUTOGEN_MSG}
9
9
 
10
- import { createHandler, loadManifest } from '${Config.PKG_NAME}/env/rsc'
10
+ import { createRuntimeHandler } from '${Config.PKG_NAME}/env/rsc'
11
11
 
12
12
  import { manifest } from './manifest.js'
13
13
  import { importMap } from './maps.js'
14
14
  import { config } from './config.js'
15
15
 
16
- const runtimeManifest = await loadManifest()
17
-
18
- export default createHandler(config, manifest, importMap, { runtimeManifest })
16
+ export default await createRuntimeHandler(config, manifest, importMap)
19
17
 
20
18
  if (import.meta.hot) {
21
19
  import.meta.hot.accept()
@@ -38,3 +38,14 @@ export type RscPayload = {
38
38
  export declare function createHandler(config: RuntimeConfig, manifest: Manifest, importMap: ImportMap, options?: HandlerOptions): {
39
39
  fetch(req: Request): Promise<Response>;
40
40
  };
41
+ /**
42
+ * Build a Solas request handler over an {@link Assets} store.
43
+ *
44
+ * Loads the runtime manifest and constructs the RSC handler, defaulting to the
45
+ * Node standard library for assets. Platform adapters pass their own store (for
46
+ * example the Cloudflare `ASSETS` binding), so the same generated entry runs
47
+ * unchanged across runtimes.
48
+ */
49
+ export declare function createRuntimeHandler(config: RuntimeConfig, manifest: Manifest, importMap: ImportMap, assets?: Assets): Promise<{
50
+ fetch(req: Request): Promise<Response>;
51
+ }>;
@@ -3,6 +3,7 @@ import { renderToReadableStream } from '@vitejs/plugin-rsc/rsc';
3
3
  import { applyBasePath, normaliseBasePath, stripBasePath } from '../../utils/base-path.js';
4
4
  import { Logger } from '../../utils/logger.js';
5
5
  import * as Config from '../../config.js';
6
+ import * as RuntimeManifest from '../../manifest.js';
6
7
  import { createHttpRouter } from '../http-router/create-http-router.js';
7
8
  import { HttpRouter } from '../http-router/router.js';
8
9
  import { normalisePathname } from '../http-router/utils.js';
@@ -412,6 +413,18 @@ export function createHandler(config, manifest, importMap, options = {}) {
412
413
  },
413
414
  };
414
415
  }
416
+ /**
417
+ * Build a Solas request handler over an {@link Assets} store.
418
+ *
419
+ * Loads the runtime manifest and constructs the RSC handler, defaulting to the
420
+ * Node standard library for assets. Platform adapters pass their own store (for
421
+ * example the Cloudflare `ASSETS` binding), so the same generated entry runs
422
+ * unchanged across runtimes.
423
+ */
424
+ export async function createRuntimeHandler(config, manifest, importMap, assets = nodeAssets) {
425
+ const runtimeManifest = await RuntimeManifest.loadManifest(assets);
426
+ return createHandler(config, manifest, importMap, { assets, runtimeManifest });
427
+ }
415
428
  function onError(err) {
416
429
  if (err == null)
417
430
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jk2908/solas",
3
- "version": "0.8.0-canary.1791495022678",
3
+ "version": "0.8.0",
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",