@jk2908/solas 0.5.3 → 0.6.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 (69) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +81 -85
  3. package/dist/config.d.ts +25 -0
  4. package/dist/config.js +152 -0
  5. package/dist/events.d.ts +4 -0
  6. package/dist/events.js +5 -0
  7. package/dist/index.d.ts +1 -2
  8. package/dist/index.js +59 -37
  9. package/dist/internal/boundary-error.d.ts +4 -0
  10. package/dist/internal/boundary-error.js +8 -0
  11. package/dist/internal/browser-router/history.d.ts +46 -0
  12. package/dist/internal/browser-router/history.js +131 -0
  13. package/dist/internal/browser-router/link.d.ts +1 -1
  14. package/dist/internal/browser-router/link.js +1 -1
  15. package/dist/internal/browser-router/router.d.ts +6 -14
  16. package/dist/internal/browser-router/router.js +39 -13
  17. package/dist/internal/browser-router/shared.d.ts +183 -164
  18. package/dist/internal/browser-router/shared.js +49 -56
  19. package/dist/internal/browser-router/use-router.d.ts +1 -10
  20. package/dist/internal/browser-router/use-search-params.js +3 -3
  21. package/dist/internal/build.d.ts +108 -110
  22. package/dist/internal/build.js +569 -581
  23. package/dist/internal/codegen/config.js +3 -3
  24. package/dist/internal/codegen/environments.js +5 -6
  25. package/dist/internal/codegen/format.d.ts +6 -0
  26. package/dist/internal/codegen/format.js +24 -0
  27. package/dist/internal/codegen/manifest.js +2 -2
  28. package/dist/internal/codegen/maps.d.ts +1 -1
  29. package/dist/internal/codegen/maps.js +2 -2
  30. package/dist/internal/codegen/types.js +4 -4
  31. package/dist/internal/codegen/utils.js +2 -2
  32. package/dist/internal/env/request-context.js +1 -1
  33. package/dist/internal/env/rsc.d.ts +6 -4
  34. package/dist/internal/env/rsc.js +27 -25
  35. package/dist/internal/env/ssr.js +4 -4
  36. package/dist/internal/http-router/router.js +8 -8
  37. package/dist/internal/metadata.d.ts +1 -1
  38. package/dist/internal/metadata.js +1 -1
  39. package/dist/internal/navigation/http-exception-boundary.d.ts +1 -1
  40. package/dist/internal/navigation/redirect-boundary.d.ts +1 -1
  41. package/dist/internal/navigation/redirect.js +6 -6
  42. package/dist/internal/postbuild.js +18 -17
  43. package/dist/internal/prerender.d.ts +130 -138
  44. package/dist/internal/prerender.js +326 -354
  45. package/dist/internal/public-files.js +2 -4
  46. package/dist/internal/render/head.js +2 -2
  47. package/dist/internal/resolver.js +1 -1
  48. package/dist/internal/server/actions.js +2 -2
  49. package/dist/internal/ui/error-boundary.d.ts +1 -1
  50. package/dist/manifest.d.ts +7 -0
  51. package/dist/manifest.js +85 -0
  52. package/dist/prerender.d.ts +1 -1
  53. package/dist/prerender.js +1 -1
  54. package/dist/solas.d.ts +2 -46
  55. package/dist/solas.js +6 -266
  56. package/dist/types.d.ts +14 -21
  57. package/dist/types.js +1 -1
  58. package/dist/utils/base-path.d.ts +12 -14
  59. package/dist/utils/base-path.js +75 -82
  60. package/dist/utils/compress.d.ts +9 -11
  61. package/dist/utils/compress.js +59 -64
  62. package/dist/utils/context.d.ts +4 -6
  63. package/dist/utils/context.js +17 -22
  64. package/dist/utils/logger.js +2 -2
  65. package/dist/utils/time.d.ts +2 -4
  66. package/dist/utils/time.js +23 -29
  67. package/package.json +18 -8
  68. package/dist/runtimes/bun.d.ts +0 -13
  69. package/dist/runtimes/bun.js +0 -39
package/dist/index.js CHANGED
@@ -5,10 +5,12 @@ import { createServer, loadConfigFromFile, } from 'vite';
5
5
  import rsc from '@vitejs/plugin-rsc';
6
6
  import { ExportReader } from './utils/export-reader.js';
7
7
  import { Logger } from './utils/logger.js';
8
- import { Time } from './utils/time.js';
9
- import { Build } from './internal/build.js';
8
+ import { debounce } from './utils/time.js';
9
+ import * as Config from './config.js';
10
+ import * as Build from './internal/build.js';
10
11
  import { writeConfig } from './internal/codegen/config.js';
11
12
  import { writeBrowserEntry, writeRSCEntry, writeSSREntry, } from './internal/codegen/environments.js';
13
+ import { format as formatSource } from './internal/codegen/format.js';
12
14
  import { writeManifest } from './internal/codegen/manifest.js';
13
15
  import { writeMaps } from './internal/codegen/maps.js';
14
16
  import { writeTypes } from './internal/codegen/types.js';
@@ -16,7 +18,6 @@ import { postbuild } from './internal/postbuild.js';
16
18
  import { collect as collectPublicFiles } from './internal/public-files.js';
17
19
  import { createRuntime } from './internal/runtimes/create.js';
18
20
  import { Runtime } from './internal/runtimes/runtime.js';
19
- import { Solas } from './solas.js';
20
21
  const DEFAULT_CONFIG = {
21
22
  runtime: 'auto',
22
23
  precompress: false,
@@ -25,15 +26,14 @@ const DEFAULT_CONFIG = {
25
26
  trailingSlash: 'never',
26
27
  };
27
28
  function solas(c) {
28
- const validatedConfig = Solas.Config.validate({
29
- ...c,
30
- url: c?.url ?? process.env.VITE_APP_URL?.toString(),
31
- });
29
+ const validatedConfig = Config.validate(c);
32
30
  const config = {
33
31
  ...DEFAULT_CONFIG,
34
32
  ...validatedConfig,
35
33
  runtime: validatedConfig.runtime ?? DEFAULT_CONFIG.runtime,
36
34
  };
35
+ const envUrl = process.env.VITE_APP_URL?.toString();
36
+ let resolvedUrl = c?.url ?? (envUrl && envUrl.length > 0 ? envUrl : undefined);
37
37
  Runtime.runtime = createRuntime(config.runtime);
38
38
  if (config.logger?.level)
39
39
  Logger.defaultLevel = config.logger.level;
@@ -93,8 +93,8 @@ function solas(c) {
93
93
  }
94
94
  async function build() {
95
95
  const cwd = process.cwd();
96
- const routesDir = path.join(cwd, Solas.Config.APP_DIR);
97
- const generatedDir = path.join(cwd, Solas.Config.GENERATED_DIR);
96
+ const routesDir = path.join(cwd, Config.APP_DIR);
97
+ const generatedDir = path.join(cwd, Config.GENERATED_DIR);
98
98
  await Promise.all([
99
99
  fs.mkdir(routesDir, { recursive: true }),
100
100
  fs.mkdir(generatedDir, { recursive: true }),
@@ -107,12 +107,27 @@ function solas(c) {
107
107
  ['config.ts', writeConfig(config)],
108
108
  ['manifest.ts', writeManifest(manifest)],
109
109
  ['maps.ts', writeMaps(imports, modules)],
110
- [`${Solas.Config.SLUG}.d.ts`, writeTypes(manifest)],
111
- [Solas.Config.ENTRY_RSC, writeRSCEntry(config)],
112
- [Solas.Config.ENTRY_SSR, writeSSREntry()],
113
- [Solas.Config.ENTRY_BROWSER, writeBrowserEntry()],
110
+ [`${Config.SLUG}.d.ts`, writeTypes(manifest)],
111
+ [Config.ENTRY_RSC, writeRSCEntry(config)],
112
+ [Config.ENTRY_SSR, writeSSREntry()],
113
+ [Config.ENTRY_BROWSER, writeBrowserEntry()],
114
114
  ];
115
- const writes = await Promise.all(files.map(([file, content]) => maybeWrite(path.join(generatedDir, file), content)));
115
+ // remove generated files no longer emitted by the current codegen, so
116
+ // old artifacts don't linger and break module resolution. keep
117
+ // closeBundle's build.json, which lives in the same directory
118
+ const currentFiles = new Set(files.map(([file]) => file));
119
+ const existing = await fs
120
+ .readdir(generatedDir, { withFileTypes: true })
121
+ .catch(() => []);
122
+ await Promise.all(existing
123
+ .filter(entry => entry.isFile() &&
124
+ entry.name !== 'build.json' &&
125
+ !currentFiles.has(entry.name))
126
+ .map(entry => fs.rm(path.join(generatedDir, entry.name), { force: true })));
127
+ const writes = await Promise.all(files.map(async ([file, content]) => {
128
+ const formatted = await formatSource(file, content);
129
+ return maybeWrite(path.join(generatedDir, file), formatted);
130
+ }));
116
131
  const changed = writes.filter(n => n !== null);
117
132
  // early return if nothing has changed
118
133
  if (!changed.length)
@@ -125,7 +140,7 @@ function solas(c) {
125
140
  // normalise all watcher paths to forward slashes so path checks behave the
126
141
  // same on Windows and POSIX
127
142
  const WATCH_CWD = process.cwd().replace(/\\/g, '/');
128
- const WATCH_APP_ROOT = `${WATCH_CWD}/${Solas.Config.APP_DIR}/`;
143
+ const WATCH_APP_ROOT = `${WATCH_CWD}/${Config.APP_DIR}/`;
129
144
  // convert watcher paths to a consistent slash format before comparing them
130
145
  const normaliseWatchPath = (p) => p.replace(/\\/g, '/');
131
146
  // resolve relative watcher paths against the project root so prefix checks are reliable
@@ -136,7 +151,7 @@ function solas(c) {
136
151
  // edits needing special treatment because verb exports can change in-place
137
152
  const routeFile = /\/\+(layout|page|401|403|404|500|loading|middleware|endpoint)\.(t|j)sx?$/;
138
153
  const endpointFile = /\/\+endpoint\.(t|j)sx?$/;
139
- const rebuild = Time.debounce((event, p) => {
154
+ const rebuild = debounce((event, p) => {
140
155
  const queue = () => {
141
156
  void (async () => {
142
157
  // collapse bursts of file events into one active rebuild plus a single
@@ -197,9 +212,7 @@ function solas(c) {
197
212
  return [];
198
213
  if (Array.isArray(plugin))
199
214
  return plugin.flatMap(flatten);
200
- if (typeof plugin === 'object' &&
201
- 'name' in plugin &&
202
- plugin.name === Solas.Config.NAME) {
215
+ if (typeof plugin === 'object' && 'name' in plugin && plugin.name === Config.NAME) {
203
216
  return [];
204
217
  }
205
218
  return [plugin];
@@ -219,26 +232,35 @@ function solas(c) {
219
232
  return utilityServer;
220
233
  }
221
234
  const plugin = {
222
- name: Solas.Config.NAME,
235
+ name: Config.NAME,
223
236
  async config(viteConfig) {
224
237
  const pkg = JSON.parse(fsSync.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
225
238
  if (typeof pkg.name !== 'string' || pkg.name.length === 0) {
226
- throw new Error(`Missing ${Solas.Config.NAME} package name`);
239
+ throw new Error(`Missing ${Config.NAME} package name`);
227
240
  }
228
241
  if (typeof pkg.version !== 'string' || pkg.version.length === 0) {
229
- throw new Error(`Missing ${Solas.Config.NAME} package version`);
242
+ throw new Error(`Missing ${Config.NAME} package version`);
230
243
  }
231
244
  viteConfig.build ??= {};
232
- viteConfig.build.outDir = Solas.Config.OUT_DIR;
245
+ viteConfig.build.outDir = Config.OUT_DIR;
233
246
  // keep framework files under one reserved url prefix
234
- viteConfig.build.assetsDir = Solas.Config.ASSETS_DIR;
247
+ viteConfig.build.assetsDir = Config.ASSETS_DIR;
235
248
  viteConfig.build.emptyOutDir = true;
236
249
  // let users move the source public folder if they want
237
- viteConfig.publicDir ??= Solas.Config.PUBLIC_DIR;
250
+ viteConfig.publicDir ??= Config.PUBLIC_DIR;
238
251
  viteConfig.server ??= {};
239
- viteConfig.server.port = config.port ?? viteConfig.server.port ?? 8787;
252
+ viteConfig.server.port ??= 8787;
253
+ // derive the public url from vite's dev server settings when no
254
+ // origin was set explicitly via the url option or VITE_APP_URL
255
+ if (resolvedUrl === undefined) {
256
+ const { host, port } = viteConfig.server;
257
+ const hostname = typeof host === 'string' && host.length > 0 && host !== '0.0.0.0'
258
+ ? host
259
+ : 'localhost';
260
+ resolvedUrl = `http://${hostname}:${port}`;
261
+ }
240
262
  viteConfig.define ??= {};
241
- viteConfig.define['import.meta.env.VITE_APP_URL'] = JSON.stringify(config.url);
263
+ viteConfig.define['import.meta.env.VITE_APP_URL'] = JSON.stringify(resolvedUrl);
242
264
  viteConfig.define['import.meta.env.SOLAS_VERSION'] = JSON.stringify(pkg.version);
243
265
  viteConfig.optimizeDeps ??= {};
244
266
  viteConfig.optimizeDeps.exclude = [
@@ -255,12 +277,12 @@ function solas(c) {
255
277
  ...viteConfig.resolve.alias,
256
278
  {
257
279
  find: '.solas',
258
- replacement: path.resolve(process.cwd(), Solas.Config.GENERATED_DIR),
280
+ replacement: path.resolve(process.cwd(), Config.GENERATED_DIR),
259
281
  },
260
282
  ]
261
283
  : {
262
284
  ...viteConfig.resolve.alias,
263
- '.solas': path.resolve(process.cwd(), Solas.Config.GENERATED_DIR),
285
+ '.solas': path.resolve(process.cwd(), Config.GENERATED_DIR),
264
286
  };
265
287
  },
266
288
  configResolved(resolvedConfig) {
@@ -268,7 +290,7 @@ function solas(c) {
268
290
  buildContext.command = resolvedConfig.command;
269
291
  },
270
292
  configureServer(server) {
271
- logger.info('[configureServer]', `Watching for changes in ./${Solas.Config.APP_DIR}...`);
293
+ logger.info('[configureServer]', `Watching for changes in ./${Config.APP_DIR}...`);
272
294
  server.watcher
273
295
  .on('add', (p) => rebuild('add', p))
274
296
  .on('change', (p) => rebuild('change', p))
@@ -294,7 +316,7 @@ function solas(c) {
294
316
  }
295
317
  // resolve sitemap routes
296
318
  let sitemapRoutes = [];
297
- if (config.sitemap && config.url) {
319
+ if (config.sitemap && resolvedUrl) {
298
320
  const auto = [
299
321
  ...new Set([...buildContext.knownRoutes, ...buildContext.prerenderRoutes]),
300
322
  ];
@@ -306,7 +328,7 @@ function solas(c) {
306
328
  }
307
329
  }
308
330
  // write build manifest
309
- const generatedDir = path.join(process.cwd(), Solas.Config.GENERATED_DIR);
331
+ const generatedDir = path.join(process.cwd(), Config.GENERATED_DIR);
310
332
  await Runtime.write(path.join(generatedDir, 'build.json'), JSON.stringify({
311
333
  base: resolvedViteConfig?.base ?? '/',
312
334
  publicFiles: await collectPublicFiles(resolvedViteConfig?.publicDir),
@@ -314,7 +336,7 @@ function solas(c) {
314
336
  sitemapRoutes,
315
337
  precompress: config.precompress,
316
338
  trailingSlash: config.trailingSlash,
317
- url: config.url,
339
+ url: resolvedUrl,
318
340
  }));
319
341
  },
320
342
  buildApp: {
@@ -328,12 +350,12 @@ function solas(c) {
328
350
  plugin,
329
351
  rsc({
330
352
  entries: {
331
- rsc: `./${Solas.Config.GENERATED_DIR}/${Solas.Config.ENTRY_RSC}`,
332
- ssr: `./${Solas.Config.GENERATED_DIR}/${Solas.Config.ENTRY_SSR}`,
333
- client: `./${Solas.Config.GENERATED_DIR}/${Solas.Config.ENTRY_BROWSER}`,
353
+ rsc: `./${Config.GENERATED_DIR}/${Config.ENTRY_RSC}`,
354
+ ssr: `./${Config.GENERATED_DIR}/${Config.ENTRY_SSR}`,
355
+ client: `./${Config.GENERATED_DIR}/${Config.ENTRY_BROWSER}`,
334
356
  },
335
357
  }),
336
358
  ];
337
359
  }
338
360
  export default solas;
339
- export { Solas } from './solas.js';
361
+ export * as Solas from './solas.js';
@@ -0,0 +1,4 @@
1
+ export declare class BoundaryError extends Error {
2
+ digest?: string;
3
+ constructor(digest?: string);
4
+ }
@@ -0,0 +1,8 @@
1
+ export class BoundaryError extends Error {
2
+ digest;
3
+ constructor(digest) {
4
+ super();
5
+ if (digest)
6
+ this.digest = digest;
7
+ }
8
+ }
@@ -0,0 +1,46 @@
1
+ import * as BrowserRouter from './shared.js';
2
+ /**
3
+ * A small in-memory mirror of browser history used by BrowserRouter.
4
+ * It keeps local entries/index in sync with push/replace/go and
5
+ * popstate updates
6
+ */
7
+ export declare class BrowserRouterHistory {
8
+ #private;
9
+ constructor(url: {
10
+ pathname: string;
11
+ search?: string;
12
+ hash?: string;
13
+ });
14
+ /**
15
+ * Returns a copy of tracked entries
16
+ */
17
+ get entries(): BrowserRouter.HistoryEntry[];
18
+ /**
19
+ * Returns the tracked history index
20
+ */
21
+ get index(): number;
22
+ /**
23
+ * Returns the tracked history length
24
+ */
25
+ get length(): number;
26
+ /**
27
+ * Returns the current tracked entry
28
+ */
29
+ get current(): BrowserRouter.HistoryEntry;
30
+ /**
31
+ * Pushes a new history entry and advances the index
32
+ */
33
+ pushState(path: string): BrowserRouter.HistoryEntry;
34
+ /**
35
+ * Replaces the current history entry without changing length
36
+ */
37
+ replaceState(path: string): BrowserRouter.HistoryEntry;
38
+ /**
39
+ * Applies relative history movement and updates local index
40
+ */
41
+ go(delta: number): BrowserRouter.HistoryEntry;
42
+ /**
43
+ * Syncs local index and entry from a popstate event
44
+ */
45
+ onPopState(): BrowserRouter.HistoryEntry;
46
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * A small in-memory mirror of browser history used by BrowserRouter.
3
+ * It keeps local entries/index in sync with push/replace/go and
4
+ * popstate updates
5
+ */
6
+ export class BrowserRouterHistory {
7
+ #entries;
8
+ #index;
9
+ constructor(url) {
10
+ const entry = {
11
+ pathname: url.pathname,
12
+ search: url.search ?? '',
13
+ hash: url.hash ?? '',
14
+ };
15
+ this.#entries = [entry];
16
+ this.#index = 0;
17
+ }
18
+ /**
19
+ * Returns a copy of tracked entries
20
+ */
21
+ get entries() {
22
+ return [...this.#entries];
23
+ }
24
+ /**
25
+ * Returns the tracked history index
26
+ */
27
+ get index() {
28
+ return this.#index;
29
+ }
30
+ /**
31
+ * Returns the tracked history length
32
+ */
33
+ get length() {
34
+ return this.#entries.length;
35
+ }
36
+ /**
37
+ * Returns the current tracked entry
38
+ */
39
+ get current() {
40
+ return this.#entries[this.#index] ?? null;
41
+ }
42
+ /**
43
+ * Pushes a new history entry and advances the index
44
+ */
45
+ pushState(path) {
46
+ if (this.#index < this.#entries.length - 1) {
47
+ // drop forward entries when pushing from the middle
48
+ this.#entries = this.#entries.slice(0, this.#index + 1);
49
+ }
50
+ window.history.pushState(null, '', path);
51
+ const entry = {
52
+ pathname: window.location.pathname,
53
+ search: window.location.search,
54
+ hash: window.location.hash,
55
+ };
56
+ this.#entries.push(entry);
57
+ this.#index++;
58
+ return entry;
59
+ }
60
+ /**
61
+ * Replaces the current history entry without changing length
62
+ */
63
+ replaceState(path) {
64
+ window.history.replaceState(null, '', path);
65
+ const entry = {
66
+ pathname: window.location.pathname,
67
+ search: window.location.search,
68
+ hash: window.location.hash,
69
+ };
70
+ if (this.#entries.length === 0) {
71
+ this.#entries = [entry];
72
+ this.#index = 0;
73
+ return entry;
74
+ }
75
+ this.#entries[this.#index] = entry;
76
+ return entry;
77
+ }
78
+ /**
79
+ * Applies relative history movement and updates local index
80
+ */
81
+ go(delta) {
82
+ if (!Number.isInteger(delta) || delta === 0) {
83
+ return this.current;
84
+ }
85
+ const nextIndex = Math.max(0, Math.min(this.#index + delta, this.#entries.length - 1));
86
+ if (nextIndex !== this.#index)
87
+ this.#index = nextIndex;
88
+ window.history.go(delta);
89
+ return this.current;
90
+ }
91
+ /**
92
+ * Syncs local index and entry from a popstate event
93
+ */
94
+ onPopState() {
95
+ const current = {
96
+ pathname: window.location.pathname,
97
+ search: window.location.search,
98
+ hash: window.location.hash,
99
+ };
100
+ const prev = this.#entries[this.#index - 1];
101
+ const next = this.#entries[this.#index + 1];
102
+ if (prev &&
103
+ prev.pathname === current.pathname &&
104
+ prev.search === current.search &&
105
+ prev.hash === current.hash) {
106
+ this.#index--;
107
+ this.#entries[this.#index] = current;
108
+ return current;
109
+ }
110
+ if (next &&
111
+ next.pathname === current.pathname &&
112
+ next.search === current.search &&
113
+ next.hash === current.hash) {
114
+ this.#index++;
115
+ this.#entries[this.#index] = current;
116
+ return current;
117
+ }
118
+ // fallback: locate an existing matching entry
119
+ const index = this.#entries.findIndex(entry => entry.pathname === current.pathname &&
120
+ entry.search === current.search &&
121
+ entry.hash === current.hash);
122
+ if (index >= 0) {
123
+ this.#index = index;
124
+ this.#entries[index] = current;
125
+ return current;
126
+ }
127
+ this.#entries.push(current);
128
+ this.#index = this.#entries.length - 1;
129
+ return current;
130
+ }
131
+ }
@@ -1,4 +1,4 @@
1
- import { BrowserRouter } from './shared.js';
1
+ import * as BrowserRouter from './shared.js';
2
2
  type AnchorProps = React.ComponentPropsWithRef<'a'> & {
3
3
  href: string;
4
4
  };
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useEffect, useRef } from 'react';
4
- import { BrowserRouter } from './shared.js';
4
+ import * as BrowserRouter from './shared.js';
5
5
  import { useRouter } from './use-router.js';
6
6
  function guard(path, prefetcher) {
7
7
  const connection = window.navigator.connection;
@@ -1,22 +1,14 @@
1
1
  import type { RscPayload } from '../env/rsc.js';
2
- import { BrowserRouter } from './shared.js';
3
- export { BrowserRouter } from './shared.js';
4
- export declare const BrowserRouterContext: import("react").Context<{
5
- go: BrowserRouter.Go;
6
- prefetch: (path: string) => void;
7
- refresh: BrowserRouter.Refresh;
8
- isNavigating: boolean;
9
- url: {
10
- pathname?: string | undefined;
11
- search?: string | undefined;
12
- };
13
- }>;
2
+ import * as BrowserRouter from './shared.js';
3
+ export * as BrowserRouter from './shared.js';
4
+ export declare const BrowserRouterContext: import("react").Context<BrowserRouter.Context>;
14
5
  export declare function BrowserRouterProvider({ children, setPayload, isNavigating, url }: {
15
6
  children: React.ReactNode;
16
7
  setPayload?: (payload: RscPayload) => void;
17
8
  isNavigating?: boolean;
18
- url?: {
19
- pathname?: string;
9
+ url: {
10
+ pathname: string;
20
11
  search?: string;
12
+ hash?: string;
21
13
  };
22
14
  }): import("react/jsx-runtime").JSX.Element;
@@ -3,16 +3,25 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { createContext, useCallback, useEffect, useMemo, useRef } from 'react';
4
4
  import { createFromFetch } from '@vitejs/plugin-rsc/browser';
5
5
  import { Logger } from '../../utils/logger.js';
6
- import { Solas } from '../../solas.js';
6
+ import * as Events from '../../events.js';
7
+ import { BrowserRouterHistory } from './history.js';
7
8
  import { ResponseCache } from './response-cache.js';
8
- import { BrowserRouter } from './shared.js';
9
- export { BrowserRouter } from './shared.js';
9
+ import * as BrowserRouter from './shared.js';
10
+ export * as BrowserRouter from './shared.js';
10
11
  export const BrowserRouterContext = createContext({
11
12
  go: async () => '',
12
13
  prefetch: () => { },
13
14
  refresh: async () => '',
14
15
  isNavigating: false,
15
- url: {},
16
+ url: {
17
+ pathname: '',
18
+ search: '',
19
+ hash: '',
20
+ },
21
+ history: {
22
+ entries: [],
23
+ index: -1,
24
+ },
16
25
  });
17
26
  const DEFAULT_GO_CONFIG = {
18
27
  replace: false,
@@ -22,6 +31,7 @@ const responseCache = new ResponseCache();
22
31
  export function BrowserRouterProvider({ children, setPayload, isNavigating = false, url, }) {
23
32
  const id = useRef(0);
24
33
  const controller = useRef(null);
34
+ const history = useRef(new BrowserRouterHistory(url));
25
35
  /**
26
36
  * Navigates to a given path
27
37
  *
@@ -35,6 +45,13 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
35
45
  const navigationId = id.current;
36
46
  const currentPath = window.location.pathname + window.location.search;
37
47
  let path = currentPath;
48
+ // support numeric relative navigation like router.go(-1) to go back,
49
+ // or router.go(1) to go forward. Will trigger a popstate event which
50
+ // is intercepted
51
+ if (typeof to === 'number') {
52
+ const entry = history.current.go(to);
53
+ return `${entry.pathname}${entry.search}`;
54
+ }
38
55
  const replace = opts?.replace ?? DEFAULT_GO_CONFIG.replace;
39
56
  controller.current?.abort();
40
57
  controller.current = null;
@@ -51,10 +68,10 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
51
68
  path = key;
52
69
  if (path !== currentPath) {
53
70
  if (replace) {
54
- window.history.replaceState(null, '', path);
71
+ history.current.replaceState(path);
55
72
  }
56
73
  else {
57
- window.history.pushState(null, '', path);
74
+ history.current.pushState(path);
58
75
  }
59
76
  }
60
77
  let promise = responseCache.get(path);
@@ -78,10 +95,10 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
78
95
  if (navigationId !== id.current)
79
96
  return resolvedPath;
80
97
  if (resolvedPath !== path) {
81
- window.history.replaceState(null, '', resolvedPath);
98
+ history.current.replaceState(resolvedPath);
82
99
  }
83
100
  setPayload?.(payload);
84
- window.dispatchEvent(new CustomEvent(Solas.Events.names.NAVIGATION, {
101
+ window.dispatchEvent(new CustomEvent(Events.names.NAVIGATION, {
85
102
  detail: { path: resolvedPath },
86
103
  }));
87
104
  return resolvedPath;
@@ -90,7 +107,7 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
90
107
  if (err instanceof Error && err.name === 'AbortError') {
91
108
  return path;
92
109
  }
93
- window.dispatchEvent(new CustomEvent(Solas.Events.names.NAVIGATION_ERROR, {
110
+ window.dispatchEvent(new CustomEvent(Events.names.NAVIGATION_ERROR, {
94
111
  detail: {
95
112
  path,
96
113
  error: err instanceof Error ? err.message : Logger.print(err),
@@ -138,9 +155,13 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
138
155
  });
139
156
  }, [go]);
140
157
  useEffect(() => {
141
- const handler = () => go(BrowserRouter.toTarget(window.location.pathname + window.location.search), {
142
- replace: true,
143
- });
158
+ const h = history.current;
159
+ function handler() {
160
+ h.onPopState();
161
+ void go(BrowserRouter.toTarget(window.location.pathname + window.location.search), {
162
+ replace: true,
163
+ });
164
+ }
144
165
  window.addEventListener('popstate', handler);
145
166
  return () => {
146
167
  controller.current?.abort();
@@ -154,8 +175,13 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
154
175
  refresh,
155
176
  isNavigating,
156
177
  url: {
157
- pathname: url?.pathname,
178
+ pathname: url.pathname,
158
179
  search: url?.search,
180
+ hash: url?.hash,
181
+ },
182
+ history: {
183
+ entries: history.current.entries,
184
+ index: history.current.index,
159
185
  },
160
186
  }), [go, prefetch, refresh, isNavigating, url]);
161
187
  return _jsx(BrowserRouterContext, { value: value, children: children });