@jk2908/solas 0.4.3 → 0.5.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +200 -8
  3. package/dist/adapters/bun.d.ts +12 -0
  4. package/dist/adapters/bun.js +39 -0
  5. package/dist/adapters/node.d.ts +11 -0
  6. package/dist/adapters/node.js +34 -0
  7. package/dist/index.js +15 -8
  8. package/dist/internal/browser-router/link.js +2 -0
  9. package/dist/internal/{prefetcher.d.ts → browser-router/response-cache.d.ts} +12 -5
  10. package/dist/internal/{prefetcher.js → browser-router/response-cache.js} +11 -4
  11. package/dist/internal/browser-router/router.d.ts +1 -0
  12. package/dist/internal/browser-router/router.js +45 -13
  13. package/dist/internal/browser-router/shared.d.ts +1 -0
  14. package/dist/internal/browser-router/shared.js +1 -0
  15. package/dist/internal/browser-router/use-router.d.ts +1 -0
  16. package/dist/internal/build.js +14 -13
  17. package/dist/internal/codegen/config.js +3 -2
  18. package/dist/internal/codegen/environments.d.ts +2 -1
  19. package/dist/internal/codegen/environments.js +6 -4
  20. package/dist/internal/codegen/maps.js +7 -5
  21. package/dist/internal/env/rsc.d.ts +1 -0
  22. package/dist/internal/env/rsc.js +1 -0
  23. package/dist/internal/http-router/router.js +8 -7
  24. package/dist/internal/postbuild.js +9 -8
  25. package/dist/internal/prerender.js +14 -13
  26. package/dist/internal/runtimes/bun.d.ts +9 -0
  27. package/dist/internal/runtimes/bun.js +33 -0
  28. package/dist/internal/runtimes/node.d.ts +9 -0
  29. package/dist/internal/runtimes/node.js +31 -0
  30. package/dist/internal/runtimes/runtime.d.ts +29 -0
  31. package/dist/internal/runtimes/runtime.js +32 -0
  32. package/dist/runtimes/bun.d.ts +13 -0
  33. package/dist/runtimes/bun.js +39 -0
  34. package/dist/solas.d.ts +4 -1
  35. package/dist/solas.js +27 -6
  36. package/dist/types.d.ts +6 -1
  37. package/dist/utils/compress.js +2 -2
  38. package/dist/utils/export-reader.js +69 -58
  39. package/package.json +9 -5
@@ -24,6 +24,7 @@ var BrowserRouter;
24
24
  */
25
25
  function toTarget(path, params, query) {
26
26
  const used = new Set();
27
+ // first, replace all the :param parts with the corresponding params
27
28
  let to = path.replaceAll(/:([A-Za-z0-9_]+)/g, (_, key) => {
28
29
  const value = params?.[key];
29
30
  if (value == null) {
@@ -1,6 +1,7 @@
1
1
  export declare function useRouter(): {
2
2
  go: import("./shared.js").BrowserRouter.Go;
3
3
  prefetch: (path: string) => void;
4
+ refresh: import("./shared.js").BrowserRouter.Refresh;
4
5
  isNavigating: boolean;
5
6
  url: {
6
7
  pathname?: string | undefined;
@@ -4,6 +4,7 @@ import { Logger } from '../utils/logger.js';
4
4
  import { Solas } from '../solas.js';
5
5
  import { normalisePathname } from './http-router/utils.js';
6
6
  import { Prerender } from './prerender.js';
7
+ import { Runtime } from './runtimes/runtime.js';
7
8
  export { Build };
8
9
  /**
9
10
  * Types, constants, and the Finder class for route discovery and manifest generation.
@@ -345,7 +346,7 @@ var Build;
345
346
  currentPrerenderMode = flag;
346
347
  }
347
348
  const shellImport = Finder.getImportPath(shellPath);
348
- const shellId = `${Build.EntryKind.SHELL}${Bun.hash(shellImport)}`;
349
+ const shellId = `${Build.EntryKind.SHELL}${Runtime.hash(shellImport)}`;
349
350
  const layoutIds = [];
350
351
  const unauthorisedIds = [];
351
352
  const forbiddenIds = [];
@@ -366,7 +367,7 @@ var Build;
366
367
  continue;
367
368
  }
368
369
  const layoutImport = Finder.getImportPath(layoutPath);
369
- const layoutId = `${Build.EntryKind.LAYOUT}${Bun.hash(layoutImport)}`;
370
+ const layoutId = `${Build.EntryKind.LAYOUT}${Runtime.hash(layoutImport)}`;
370
371
  if (!processed.has(layoutPath)) {
371
372
  prerenderCache.set(layoutPath, await Prerender.Build.getStaticFlag(layoutPath, this.buildContext));
372
373
  imports.components.dynamic.set(layoutId, layoutImport);
@@ -381,7 +382,7 @@ var Build;
381
382
  continue;
382
383
  }
383
384
  const unauthorisedImport = Finder.getImportPath(unauthorisedPath);
384
- const unauthorisedId = `${Build.EntryKind['401']}${Bun.hash(unauthorisedImport)}`;
385
+ const unauthorisedId = `${Build.EntryKind['401']}${Runtime.hash(unauthorisedImport)}`;
385
386
  unauthorisedIds.push(unauthorisedId);
386
387
  if (!processed.has(unauthorisedPath)) {
387
388
  imports.components.dynamic.set(unauthorisedId, unauthorisedImport);
@@ -394,7 +395,7 @@ var Build;
394
395
  continue;
395
396
  }
396
397
  const forbiddenImport = Finder.getImportPath(forbiddenPath);
397
- const forbiddenId = `${Build.EntryKind['403']}${Bun.hash(forbiddenImport)}`;
398
+ const forbiddenId = `${Build.EntryKind['403']}${Runtime.hash(forbiddenImport)}`;
398
399
  forbiddenIds.push(forbiddenId);
399
400
  if (!processed.has(forbiddenPath)) {
400
401
  imports.components.dynamic.set(forbiddenId, forbiddenImport);
@@ -409,7 +410,7 @@ var Build;
409
410
  continue;
410
411
  }
411
412
  const notFoundImport = Finder.getImportPath(notFoundPath);
412
- const notFoundId = `${Build.EntryKind['404']}${Bun.hash(notFoundImport)}`;
413
+ const notFoundId = `${Build.EntryKind['404']}${Runtime.hash(notFoundImport)}`;
413
414
  notFoundIds.push(notFoundId);
414
415
  // dedupe imports but still assign the slot for this route
415
416
  if (!processed.has(notFoundPath)) {
@@ -423,7 +424,7 @@ var Build;
423
424
  continue;
424
425
  }
425
426
  const serverErrorImport = Finder.getImportPath(serverErrorPath);
426
- const serverErrorId = `${Build.EntryKind['500']}${Bun.hash(serverErrorImport)}`;
427
+ const serverErrorId = `${Build.EntryKind['500']}${Runtime.hash(serverErrorImport)}`;
427
428
  serverErrorIds.push(serverErrorId);
428
429
  if (!processed.has(serverErrorPath)) {
429
430
  imports.components.dynamic.set(serverErrorId, serverErrorImport);
@@ -438,7 +439,7 @@ var Build;
438
439
  continue;
439
440
  }
440
441
  const loaderImport = Finder.getImportPath(loaderPath);
441
- const loaderId = `${Build.EntryKind.LOADING}${Bun.hash(loaderImport)}`;
442
+ const loaderId = `${Build.EntryKind.LOADING}${Runtime.hash(loaderImport)}`;
442
443
  loadingIds.push(loaderId);
443
444
  // dedupe imports but still assign the slot for this route
444
445
  if (!processed.has(loaderPath)) {
@@ -452,7 +453,7 @@ var Build;
452
453
  continue;
453
454
  }
454
455
  const middlewareImport = Finder.getImportPath(middlewarePath);
455
- const middlewareId = `${Build.EntryKind.MIDDLEWARE}${Bun.hash(middlewareImport)}`;
456
+ const middlewareId = `${Build.EntryKind.MIDDLEWARE}${Runtime.hash(middlewareImport)}`;
456
457
  middlewareIds.push(middlewareId);
457
458
  if (!processed.has(middlewarePath)) {
458
459
  // route scanning only tells us this is a +middleware file path
@@ -466,8 +467,8 @@ var Build;
466
467
  }
467
468
  // generate entry id based on page if exists, otherwise dir
468
469
  const entryId = pagePath
469
- ? `${Build.EntryKind.PAGE}${Bun.hash(Finder.getImportPath(pagePath))}`
470
- : `${Build.EntryKind.PAGE}${Bun.hash(route)}`;
470
+ ? `${Build.EntryKind.PAGE}${Runtime.hash(Finder.getImportPath(pagePath))}`
471
+ : `${Build.EntryKind.PAGE}${Runtime.hash(route)}`;
471
472
  if (pagePath) {
472
473
  const pagePrerender = await Prerender.Build.getStaticFlag(pagePath, this.buildContext);
473
474
  applyPrerenderMode(pagePrerender);
@@ -568,12 +569,12 @@ var Build;
568
569
  continue;
569
570
  }
570
571
  const m = method.toLowerCase();
571
- const endpointId = `${Build.EntryKind.ENDPOINT}${Bun.hash(Finder.getImportPath(endpointFilePath))}_${m}`;
572
+ const endpointId = `${Build.EntryKind.ENDPOINT}${Runtime.hash(Finder.getImportPath(endpointFilePath))}_${m}`;
572
573
  const middlewareIds = await Promise.all(endpointMiddlewarePaths.map(async middlewarePath => {
573
574
  if (!middlewarePath)
574
575
  return null;
575
576
  const middlewareImport = Finder.getImportPath(middlewarePath);
576
- const middlewareId = `${Build.EntryKind.MIDDLEWARE}${Bun.hash(middlewareImport)}`;
577
+ const middlewareId = `${Build.EntryKind.MIDDLEWARE}${Runtime.hash(middlewareImport)}`;
577
578
  if (!processed.has(middlewarePath)) {
578
579
  // endpoint middleware discovery gives us file paths, not proof
579
580
  // of the export so check the module shape first
@@ -605,7 +606,7 @@ var Build;
605
606
  modules[route] = {
606
607
  ...(modules[route] ?? {}),
607
608
  middlewareIds: endpointMiddlewarePaths.map(middlewarePath => middlewarePath
608
- ? `${Build.EntryKind.MIDDLEWARE}${Bun.hash(Finder.getImportPath(middlewarePath))}`
609
+ ? `${Build.EntryKind.MIDDLEWARE}${Runtime.hash(Finder.getImportPath(middlewarePath))}`
609
610
  : null),
610
611
  };
611
612
  }
@@ -4,14 +4,15 @@ import { AUTOGEN_MSG, source, toSourceLiteral } from './utils.js';
4
4
  * Generates the code to create an exported config object
5
5
  */
6
6
  export function writeConfig(config) {
7
+ const { runtime: _runtime, ...runtimeConfig } = config;
7
8
  const loggerLevel = config.logger?.level;
8
9
  const importLines = [
9
- `import type { PluginConfig } from '${Solas.Config.PKG_NAME}'`,
10
+ `import type { RuntimeConfig } from '${Solas.Config.PKG_NAME}'`,
10
11
  loggerLevel ? `import { Logger } from '${Solas.Config.PKG_NAME}/utils/logger'` : '',
11
12
  ]
12
13
  .filter(Boolean)
13
14
  .join('\n');
14
- const configStatement = `const config = ${toSourceLiteral(config)} as const satisfies PluginConfig`;
15
+ const configStatement = `const config = ${toSourceLiteral(runtimeConfig)} as const satisfies RuntimeConfig`;
15
16
  const loggerStatement = loggerLevel
16
17
  ? `Logger.defaultLevel = ${toSourceLiteral(loggerLevel)}`
17
18
  : '';
@@ -1,7 +1,8 @@
1
+ import type { ConfiguredPluginConfig } from '../../types.js';
1
2
  /**
2
3
  * Generates the RSC entry code
3
4
  */
4
- export declare function writeRSCEntry(): string;
5
+ export declare function writeRSCEntry(config: ConfiguredPluginConfig): string;
5
6
  /**
6
7
  * Generates the SSR entry code
7
8
  */
@@ -1,19 +1,21 @@
1
1
  import { Solas } from '../../solas.js';
2
- import { AUTOGEN_MSG, source } from './utils.js';
2
+ import { AUTOGEN_MSG, source, toStringLiteral } from './utils.js';
3
3
  /**
4
4
  * Generates the RSC entry code
5
5
  */
6
- export function writeRSCEntry() {
6
+ export function writeRSCEntry(config) {
7
+ const runtime = toStringLiteral(config.runtime);
7
8
  return source `
8
9
  ${AUTOGEN_MSG}
9
10
 
10
- import { createHandler } from '${Solas.Config.PKG_NAME}/env/rsc'
11
- import { Solas } from '${Solas.Config.PKG_NAME}'
11
+ import { createHandler, Runtime } from '${Solas.Config.PKG_NAME}/env/rsc'
12
+ import { Solas } from '${Solas.Config.PKG_NAME}/$'
12
13
 
13
14
  import { manifest } from './manifest.js'
14
15
  import { importMap } from './maps.js'
15
16
  import { config } from './config.js'
16
17
 
18
+ Runtime.runtime = Solas.Runtime.create(${runtime})
17
19
  const runtimeManifest = await Solas.Runtime.loadManifest(Solas.Config.OUT_DIR)
18
20
 
19
21
  export default createHandler(config, manifest, importMap, runtimeManifest)
@@ -37,8 +37,8 @@ export function writeMaps(imports, modules) {
37
37
  parts.push(`endpoint: ${toIdentifier(m.endpointId, `endpoint id for ${moduleId}`)}`);
38
38
  }
39
39
  if (m['401Ids']?.length) {
40
- const unauthorized = toIdentifierList(m['401Ids'], `401s for ${moduleId}`);
41
- parts.push(`'401s': [${unauthorized}]`);
40
+ const unauthorised = toIdentifierList(m['401Ids'], `401s for ${moduleId}`);
41
+ parts.push(`'401s': [${unauthorised}]`);
42
42
  }
43
43
  if (m['403Ids']?.length) {
44
44
  const forbidden = toIdentifierList(m['403Ids'], `403s for ${moduleId}`);
@@ -70,13 +70,15 @@ export function writeMaps(imports, modules) {
70
70
  ${AUTOGEN_MSG}
71
71
 
72
72
  import type { ImportMap } from '${Solas.Config.PKG_NAME}'
73
- ${importLines
73
+
74
+ ${importLines
74
75
  ? `
75
- ${importLines}`
76
+ ${importLines}
77
+ `
76
78
  : ''}
77
79
 
78
80
  export const importMap = {
79
- ${entries}
81
+ ${entries}
80
82
  } as const satisfies ImportMap
81
83
  `;
82
84
  }
@@ -2,6 +2,7 @@ import type { ReactFormState } from 'react-dom/client';
2
2
  import type { ImportMap, Manifest, RuntimeConfig } from '../../types.js';
3
3
  import { Solas } from '../../solas.js';
4
4
  import { Metadata } from '../metadata.js';
5
+ export { Runtime } from '../runtimes/runtime.js';
5
6
  export type RscPayload = {
6
7
  returnValue?: {
7
8
  ok: boolean;
@@ -17,6 +17,7 @@ import { processActionRequest } from '../server/actions.js';
17
17
  import DefaultErr from '../ui/defaults/error.js';
18
18
  import { RequestContext } from './request-context.js';
19
19
  import { getKnownDigest, isKnownError } from './utils.js';
20
+ export { Runtime } from '../runtimes/runtime.js';
20
21
  const logger = new Logger();
21
22
  const BASE_PATH = BasePath.normalise(import.meta.env.BASE_URL);
22
23
  function resolveFilePath(root, relativePath) {
@@ -2,6 +2,7 @@ import { match as createMatch } from 'path-to-regexp';
2
2
  import { BasePath } from '../../utils/base-path.js';
3
3
  import { Solas } from '../../solas.js';
4
4
  import { HttpException } from '../navigation/http-exception.js';
5
+ import { Runtime } from '../runtimes/runtime.js';
5
6
  import { maybeAction } from '../server/actions.js';
6
7
  import { enforce } from '../server/csrf.js';
7
8
  import { getAlternatePathname, normalisePathname, toPathPattern } from './utils.js';
@@ -253,24 +254,24 @@ export class HttpRouter {
253
254
  */
254
255
  static async serveStatic(filePath, req, precompress = false, headers = {}) {
255
256
  const accept = req.headers.get('accept-encoding') ?? '';
256
- let file = Bun.file(filePath);
257
+ let resolvedPath = filePath;
257
258
  let encoding = null;
258
259
  if (precompress) {
259
260
  // prefer a precompressed variant when the client accepts it and one was emitted
260
261
  if (accept.includes('br')) {
261
- const brotli = Bun.file(`${filePath}.br`);
262
- if (await brotli.exists()) {
263
- file = brotli;
262
+ const brotliPath = `${filePath}.br`;
263
+ if (await Runtime.exists(brotliPath)) {
264
+ resolvedPath = brotliPath;
264
265
  encoding = 'br';
265
266
  }
266
267
  }
267
268
  }
268
- if (!(await file.exists())) {
269
+ if (!(await Runtime.exists(resolvedPath))) {
269
270
  return new Response('Not found', { status: 404 });
270
271
  }
271
272
  // get mime type from original path, not compressed variant
272
- const mimeType = Bun.file(filePath).type;
273
- const res = new Response(file, {
273
+ const mimeType = Runtime.mimeType(filePath);
274
+ const res = new Response(await Runtime.readBuffer(resolvedPath), {
274
275
  headers: {
275
276
  'Content-Type': headers['Content-Type'] ?? mimeType,
276
277
  },
@@ -4,6 +4,7 @@ import { Compress } from '../utils/compress.js';
4
4
  import { Logger } from '../utils/logger.js';
5
5
  import { Solas } from '../solas.js';
6
6
  import { Prerender } from './prerender.js';
7
+ import { Runtime } from './runtimes/runtime.js';
7
8
  const logger = new Logger();
8
9
  export async function postbuild(cwd = process.cwd()) {
9
10
  const manifestPath = path.join(cwd, Solas.Config.GENERATED_DIR, 'build.json');
@@ -59,8 +60,8 @@ export async function postbuild(cwd = process.cwd()) {
59
60
  if (artifact.mode === 'ppr') {
60
61
  await fs.mkdir(artifactDir, { recursive: true });
61
62
  const writes = [
62
- Bun.write(path.join(artifactDir, 'prelude.html'), artifact.html),
63
- Bun.write(path.join(artifactDir, 'metadata.json'), JSON.stringify({
63
+ Runtime.write(path.join(artifactDir, 'prelude.html'), artifact.html),
64
+ Runtime.write(path.join(artifactDir, 'metadata.json'), JSON.stringify({
64
65
  schema: artifact.schema,
65
66
  route: artifact.route,
66
67
  createdAt: artifact.createdAt,
@@ -68,7 +69,7 @@ export async function postbuild(cwd = process.cwd()) {
68
69
  })),
69
70
  ];
70
71
  if (artifact.postponed !== undefined) {
71
- writes.push(Bun.write(path.join(artifactDir, 'postponed.json'), JSON.stringify(artifact.postponed)));
72
+ writes.push(Runtime.write(path.join(artifactDir, 'postponed.json'), JSON.stringify(artifact.postponed)));
72
73
  }
73
74
  await Promise.all(writes);
74
75
  artifactManifest[route] = {
@@ -82,13 +83,13 @@ export async function postbuild(cwd = process.cwd()) {
82
83
  }
83
84
  await fs.mkdir(artifactDir, { recursive: true });
84
85
  await Promise.all([
85
- Bun.write(path.join(artifactDir, 'metadata.json'), JSON.stringify({
86
+ Runtime.write(path.join(artifactDir, 'metadata.json'), JSON.stringify({
86
87
  schema: artifact.schema,
87
88
  route: artifact.route,
88
89
  createdAt: artifact.createdAt,
89
90
  mode: artifact.mode,
90
91
  })),
91
- Bun.write(Prerender.Artifact.getFilePath(outDir, route, Prerender.Artifact.FULL_PRERENDER_FILENAME), artifact.html),
92
+ Runtime.write(Prerender.Artifact.getFilePath(outDir, route, Prerender.Artifact.FULL_PRERENDER_FILENAME), artifact.html),
92
93
  ]);
93
94
  artifactManifest[route] = {
94
95
  mode: artifact.mode,
@@ -108,7 +109,7 @@ export async function postbuild(cwd = process.cwd()) {
108
109
  artifacts: artifactManifest,
109
110
  publicFiles: manifest.publicFiles,
110
111
  };
111
- await Bun.write(Solas.Runtime.getManifestPath(outDir), JSON.stringify(runtimeManifest));
112
+ await Runtime.write(Solas.Runtime.getManifestPath(outDir), JSON.stringify(runtimeManifest));
112
113
  if (manifest.sitemapRoutes.length > 0 && manifest.url) {
113
114
  const origin = manifest.url.replace(/\/$/, '');
114
115
  const urls = manifest.sitemapRoutes
@@ -120,7 +121,7 @@ export async function postbuild(cwd = process.cwd()) {
120
121
  urls,
121
122
  '</urlset>',
122
123
  ].join('\n');
123
- await Bun.write(path.join(outDir, 'sitemap.xml'), sitemap);
124
+ await Runtime.write(path.join(outDir, 'sitemap.xml'), sitemap);
124
125
  logger.info('[sitemap]', `generated ${manifest.sitemapRoutes.length} urls`);
125
126
  }
126
127
  if (manifest.precompress) {
@@ -144,7 +145,7 @@ export async function postbuild(cwd = process.cwd()) {
144
145
  normalisedPath.endsWith(`/${Prerender.Artifact.FULL_PRERENDER_FILENAME}`));
145
146
  },
146
147
  })) {
147
- await Bun.write(`${input}.br`, compressed);
148
+ await Runtime.write(`${input}.br`, compressed);
148
149
  logger.info('[precompress]', `${path.basename(input)}.br`);
149
150
  }
150
151
  }
@@ -4,6 +4,7 @@ import { BasePath } from '../utils/base-path.js';
4
4
  import { Logger } from '../utils/logger.js';
5
5
  import { Solas } from '../solas.js';
6
6
  import { toPathPattern } from './http-router/utils.js';
7
+ import { Runtime as AdapterRuntime } from './runtimes/runtime.js';
7
8
  const logger = new Logger();
8
9
  export { Prerender };
9
10
  var Prerender;
@@ -60,18 +61,18 @@ var Prerender;
60
61
  * Load the postponed state for a given route from the file system, if it exists
61
62
  */
62
63
  async function loadPostponedState(outDir, pathname) {
63
- let file;
64
+ let filePath;
64
65
  try {
65
- file = Bun.file(path.join(getPath(outDir, pathname), 'postponed.json'));
66
+ filePath = path.join(getPath(outDir, pathname), 'postponed.json');
66
67
  }
67
68
  catch (err) {
68
69
  logger.warn(`[prerender:artifacts] rejected postponed state path for ${pathname}`, Logger.print(err));
69
70
  return null;
70
71
  }
71
- if (!(await file.exists()))
72
+ if (!(await AdapterRuntime.exists(filePath)))
72
73
  return null;
73
74
  try {
74
- return JSON.parse(await file.text());
75
+ return JSON.parse(await AdapterRuntime.readText(filePath));
75
76
  }
76
77
  catch {
77
78
  return null;
@@ -82,18 +83,18 @@ var Prerender;
82
83
  * Load the prelude HTML for a given route from the file system, if it exists
83
84
  */
84
85
  async function loadPrelude(outDir, pathname) {
85
- let file;
86
+ let filePath;
86
87
  try {
87
- file = Bun.file(path.join(getPath(outDir, pathname), 'prelude.html'));
88
+ filePath = path.join(getPath(outDir, pathname), 'prelude.html');
88
89
  }
89
90
  catch (err) {
90
91
  logger.warn(`[prerender:artifacts] rejected prelude path for ${pathname}`, Logger.print(err));
91
92
  return null;
92
93
  }
93
- if (!(await file.exists()))
94
+ if (!(await AdapterRuntime.exists(filePath)))
94
95
  return null;
95
96
  try {
96
- return await file.text();
97
+ return await AdapterRuntime.readText(filePath);
97
98
  }
98
99
  catch {
99
100
  return null;
@@ -104,18 +105,18 @@ var Prerender;
104
105
  * Load the prerender artifact metadata for a given route from the file system, if it exists and is valid
105
106
  */
106
107
  async function loadMetadata(outDir, pathname) {
107
- let file;
108
+ let filePath;
108
109
  try {
109
- file = Bun.file(path.join(getPath(outDir, pathname), 'metadata.json'));
110
+ filePath = path.join(getPath(outDir, pathname), 'metadata.json');
110
111
  }
111
112
  catch (err) {
112
113
  logger.warn(`[prerender:artifacts] rejected metadata path for ${pathname}`, Logger.print(err));
113
114
  return null;
114
115
  }
115
- if (!(await file.exists()))
116
+ if (!(await AdapterRuntime.exists(filePath)))
116
117
  return null;
117
118
  try {
118
- const value = JSON.parse(await file.text());
119
+ const value = JSON.parse(await AdapterRuntime.readText(filePath));
119
120
  if (!value || typeof value !== 'object')
120
121
  return null;
121
122
  const schema = value.schema;
@@ -270,7 +271,7 @@ var Prerender;
270
271
  const params = await buildContext.exportReader.value(filePath, 'params', (v) => typeof v === 'function');
271
272
  if (!params)
272
273
  return [];
273
- const resolved = await Promise.try(() => params());
274
+ const resolved = await Promise.resolve().then(() => params());
274
275
  if (!Array.isArray(resolved))
275
276
  return [];
276
277
  return resolved;
@@ -0,0 +1,9 @@
1
+ import { RuntimeBase } from './runtime.js';
2
+ export declare class RuntimeBun extends RuntimeBase {
3
+ exists(filePath: string): Promise<boolean>;
4
+ readText(filePath: string): Promise<string>;
5
+ readBuffer(filePath: string): Promise<ArrayBuffer>;
6
+ mimeType(filePath: string): string;
7
+ write(filePath: string, content: string | NodeJS.ArrayBufferView): Promise<void>;
8
+ hash(value: string): string;
9
+ }
@@ -0,0 +1,33 @@
1
+ import { RuntimeBase } from './runtime.js';
2
+ export class RuntimeBun extends RuntimeBase {
3
+ async exists(filePath) {
4
+ return Bun.file(filePath).exists();
5
+ }
6
+ readText(filePath) {
7
+ return Bun.file(filePath).text();
8
+ }
9
+ readBuffer(filePath) {
10
+ return Bun.file(filePath).arrayBuffer();
11
+ }
12
+ mimeType(filePath) {
13
+ return Bun.file(filePath).type || 'application/octet-stream';
14
+ }
15
+ async write(filePath, content) {
16
+ // normalise wider arraybuffer views into a shape Bun.write accepts directly
17
+ await Bun.write(filePath, typeof content === 'string'
18
+ ? content
19
+ : content instanceof Uint8Array
20
+ ? content
21
+ : new Uint8Array(content.buffer, content.byteOffset, content.byteLength));
22
+ }
23
+ hash(value) {
24
+ const hash = Bun.hash(value);
25
+ // Bun.hash returns an integer-like value, so keep it in BigInt space and avoid
26
+ // precision loss. Clamp it to an unsigned 64-bit value before hex
27
+ // formatting. Pad to 16 chars to match the Node runtime
28
+ // output shape
29
+ return BigInt.asUintN(64, typeof hash === 'bigint' ? hash : BigInt(hash))
30
+ .toString(16)
31
+ .padStart(16, '0');
32
+ }
33
+ }
@@ -0,0 +1,9 @@
1
+ import { RuntimeBase } from './runtime.js';
2
+ export declare class RuntimeNode extends RuntimeBase {
3
+ exists(filePath: string): Promise<boolean>;
4
+ readText(filePath: string): Promise<string>;
5
+ readBuffer(filePath: string): Promise<ArrayBuffer>;
6
+ mimeType(filePath: string): string;
7
+ write(filePath: string, content: string | NodeJS.ArrayBufferView): Promise<void>;
8
+ hash(value: string): string;
9
+ }
@@ -0,0 +1,31 @@
1
+ import { createHash } from 'node:crypto';
2
+ import fs from 'node:fs/promises';
3
+ import { lookup } from 'mime-types';
4
+ import { RuntimeBase } from './runtime.js';
5
+ export class RuntimeNode extends RuntimeBase {
6
+ async exists(filePath) {
7
+ try {
8
+ await fs.access(filePath);
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ readText(filePath) {
16
+ return fs.readFile(filePath, 'utf-8');
17
+ }
18
+ async readBuffer(filePath) {
19
+ const buffer = await fs.readFile(filePath);
20
+ return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
21
+ }
22
+ mimeType(filePath) {
23
+ return lookup(filePath) || 'application/octet-stream';
24
+ }
25
+ async write(filePath, content) {
26
+ await fs.writeFile(filePath, content);
27
+ }
28
+ hash(value) {
29
+ return createHash('sha256').update(value).digest('hex').slice(0, 16);
30
+ }
31
+ }
@@ -0,0 +1,29 @@
1
+ export declare namespace Runtime {
2
+ type Impl = {
3
+ exists: (filePath: string) => Promise<boolean>;
4
+ readText: (filePath: string) => Promise<string>;
5
+ readBuffer: (filePath: string) => Promise<ArrayBuffer>;
6
+ mimeType: (filePath: string) => string;
7
+ write: (filePath: string, content: string | NodeJS.ArrayBufferView) => Promise<void>;
8
+ hash: (value: string) => string;
9
+ };
10
+ }
11
+ export declare abstract class RuntimeBase implements Runtime.Impl {
12
+ abstract exists(filePath: string): Promise<boolean>;
13
+ abstract readText(filePath: string): Promise<string>;
14
+ abstract readBuffer(filePath: string): Promise<ArrayBuffer>;
15
+ abstract mimeType(filePath: string): string;
16
+ abstract write(filePath: string, content: string | NodeJS.ArrayBufferView): Promise<void>;
17
+ abstract hash(value: string): string;
18
+ }
19
+ export declare class Runtime {
20
+ #private;
21
+ static set runtime(runtime: Runtime.Impl);
22
+ static get runtime(): Runtime.Impl;
23
+ static exists(filePath: string): Promise<boolean>;
24
+ static readText(filePath: string): Promise<string>;
25
+ static readBuffer(filePath: string): Promise<ArrayBuffer>;
26
+ static mimeType(filePath: string): string;
27
+ static write(filePath: string, content: string | NodeJS.ArrayBufferView): Promise<void>;
28
+ static hash(value: string): string;
29
+ }
@@ -0,0 +1,32 @@
1
+ export class RuntimeBase {
2
+ }
3
+ export class Runtime {
4
+ static #runtime;
5
+ static set runtime(runtime) {
6
+ this.#runtime = runtime;
7
+ }
8
+ static get runtime() {
9
+ if (!this.#runtime) {
10
+ throw new Error('No runtime configured. Please set the runtime implementation before using runtime methods.');
11
+ }
12
+ return this.#runtime;
13
+ }
14
+ static exists(filePath) {
15
+ return this.runtime.exists(filePath);
16
+ }
17
+ static readText(filePath) {
18
+ return this.runtime.readText(filePath);
19
+ }
20
+ static readBuffer(filePath) {
21
+ return this.runtime.readBuffer(filePath);
22
+ }
23
+ static mimeType(filePath) {
24
+ return this.runtime.mimeType(filePath);
25
+ }
26
+ static write(filePath, content) {
27
+ return this.runtime.write(filePath, content);
28
+ }
29
+ static hash(value) {
30
+ return this.runtime.hash(value);
31
+ }
32
+ }
@@ -0,0 +1,13 @@
1
+ import { RuntimeBase } from '../internal/runtimes/runtime.js';
2
+ declare class BunAdapter extends RuntimeBase {
3
+ readonly name: "bun";
4
+ readonly module: string;
5
+ exists(filePath: string): Promise<boolean>;
6
+ readText(filePath: string): Promise<string>;
7
+ readBuffer(filePath: string): Promise<ArrayBuffer>;
8
+ mimeType(filePath: string): string;
9
+ write(filePath: string, content: string | NodeJS.ArrayBufferView): Promise<void>;
10
+ hash(value: string): string;
11
+ }
12
+ export default function bunAdapter(): BunAdapter;
13
+ export {};
@@ -0,0 +1,39 @@
1
+ import { RuntimeBase } from '../internal/runtimes/runtime.js';
2
+ import { Solas } from '../solas.js';
3
+ class BunAdapter extends RuntimeBase {
4
+ name = 'bun';
5
+ module = `${Solas.Config.PKG_NAME}/runtimes/bun`;
6
+ async exists(filePath) {
7
+ return Bun.file(filePath).exists();
8
+ }
9
+ readText(filePath) {
10
+ return Bun.file(filePath).text();
11
+ }
12
+ readBuffer(filePath) {
13
+ return Bun.file(filePath).arrayBuffer();
14
+ }
15
+ mimeType(filePath) {
16
+ return Bun.file(filePath).type || 'application/octet-stream';
17
+ }
18
+ async write(filePath, content) {
19
+ // normalise wider arraybuffer views into a shape Bun.write accepts directly
20
+ await Bun.write(filePath, typeof content === 'string'
21
+ ? content
22
+ : content instanceof Uint8Array
23
+ ? content
24
+ : new Uint8Array(content.buffer, content.byteOffset, content.byteLength));
25
+ }
26
+ hash(value) {
27
+ const hash = Bun.hash(value);
28
+ // Bun.hash returns an integer-like value, so keep it in BigInt space and avoid
29
+ // precision loss. Clamp it to an unsigned 64-bit value before hex
30
+ // formatting. Pad to 16 chars to match the Node adapter
31
+ // output shape
32
+ return BigInt.asUintN(64, typeof hash === 'bigint' ? hash : BigInt(hash))
33
+ .toString(16)
34
+ .padStart(16, '0');
35
+ }
36
+ }
37
+ export default function bunAdapter() {
38
+ return new BunAdapter();
39
+ }