@typed/navigation 0.18.0 → 0.18.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.
Files changed (113) hide show
  1. package/.nvmrc +1 -0
  2. package/biome.json +39 -0
  3. package/dist/Blocking.d.ts +23 -0
  4. package/dist/Blocking.js +41 -0
  5. package/dist/Blocking.js.map +1 -0
  6. package/dist/Destination.d.ts +11 -0
  7. package/dist/Destination.js +10 -0
  8. package/dist/Destination.js.map +1 -0
  9. package/dist/Error.d.ts +33 -0
  10. package/dist/Error.js +22 -0
  11. package/dist/Error.js.map +1 -0
  12. package/dist/Event.d.ts +45 -0
  13. package/dist/Event.js +17 -0
  14. package/dist/Event.js.map +1 -0
  15. package/dist/Forms.d.ts +79 -0
  16. package/dist/Forms.js +111 -0
  17. package/dist/Forms.js.map +1 -0
  18. package/dist/Handler.d.ts +6 -0
  19. package/dist/Handler.js +2 -0
  20. package/dist/Handler.js.map +1 -0
  21. package/dist/{dts/Layer.d.ts → Layer.d.ts} +11 -8
  22. package/dist/{esm/Layer.js → Layer.js} +2 -2
  23. package/dist/Layer.js.map +1 -0
  24. package/dist/NavigateOptions.d.ts +7 -0
  25. package/dist/NavigateOptions.js +7 -0
  26. package/dist/NavigateOptions.js.map +1 -0
  27. package/dist/Navigation.d.ts +79 -0
  28. package/dist/Navigation.js +49 -0
  29. package/dist/Navigation.js.map +1 -0
  30. package/dist/NavigationType.d.ts +3 -0
  31. package/dist/NavigationType.js +3 -0
  32. package/dist/NavigationType.js.map +1 -0
  33. package/dist/ProposedDestination.d.ts +13 -0
  34. package/dist/ProposedDestination.js +4 -0
  35. package/dist/ProposedDestination.js.map +1 -0
  36. package/dist/Url.d.ts +13 -0
  37. package/dist/Url.js +72 -0
  38. package/dist/Url.js.map +1 -0
  39. package/dist/index.d.ts +12 -0
  40. package/dist/index.js +13 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/internal/fromWindow.d.ts +4 -0
  43. package/dist/{esm/internal → internal}/fromWindow.js +125 -136
  44. package/dist/internal/fromWindow.js.map +1 -0
  45. package/dist/internal/memory.d.ts +6 -0
  46. package/dist/internal/memory.js +59 -0
  47. package/dist/internal/memory.js.map +1 -0
  48. package/dist/{dts/internal → internal}/shared.d.ts +44 -46
  49. package/dist/{esm/internal → internal}/shared.js +121 -168
  50. package/dist/internal/shared.js.map +1 -0
  51. package/package.json +35 -53
  52. package/readme.md +243 -0
  53. package/src/Blocking.ts +65 -65
  54. package/src/Destination.ts +14 -0
  55. package/src/Error.ts +28 -0
  56. package/src/Event.ts +26 -0
  57. package/src/Forms.ts +216 -0
  58. package/src/Handler.ts +16 -0
  59. package/src/Layer.ts +20 -9
  60. package/src/NavigateOptions.ts +9 -0
  61. package/src/Navigation.test.ts +697 -0
  62. package/src/Navigation.ts +135 -472
  63. package/src/NavigationType.ts +5 -0
  64. package/src/ProposedDestination.ts +8 -0
  65. package/src/Url.ts +106 -0
  66. package/src/index.ts +12 -17
  67. package/src/internal/fromWindow.ts +163 -234
  68. package/src/internal/memory.ts +62 -49
  69. package/src/internal/shared.ts +218 -377
  70. package/tsconfig.json +30 -0
  71. package/Blocking/package.json +0 -6
  72. package/LICENSE +0 -21
  73. package/Layer/package.json +0 -6
  74. package/Navigation/package.json +0 -6
  75. package/README.md +0 -5
  76. package/dist/cjs/Blocking.js +0 -58
  77. package/dist/cjs/Blocking.js.map +0 -1
  78. package/dist/cjs/Layer.js +0 -27
  79. package/dist/cjs/Layer.js.map +0 -1
  80. package/dist/cjs/Navigation.js +0 -278
  81. package/dist/cjs/Navigation.js.map +0 -1
  82. package/dist/cjs/index.js +0 -39
  83. package/dist/cjs/index.js.map +0 -1
  84. package/dist/cjs/internal/fromWindow.js +0 -436
  85. package/dist/cjs/internal/fromWindow.js.map +0 -1
  86. package/dist/cjs/internal/memory.js +0 -72
  87. package/dist/cjs/internal/memory.js.map +0 -1
  88. package/dist/cjs/internal/shared.js +0 -525
  89. package/dist/cjs/internal/shared.js.map +0 -1
  90. package/dist/dts/Blocking.d.ts +0 -34
  91. package/dist/dts/Blocking.d.ts.map +0 -1
  92. package/dist/dts/Layer.d.ts.map +0 -1
  93. package/dist/dts/Navigation.d.ts +0 -451
  94. package/dist/dts/Navigation.d.ts.map +0 -1
  95. package/dist/dts/index.d.ts +0 -17
  96. package/dist/dts/index.d.ts.map +0 -1
  97. package/dist/dts/internal/fromWindow.d.ts +0 -12
  98. package/dist/dts/internal/fromWindow.d.ts.map +0 -1
  99. package/dist/dts/internal/memory.d.ts +0 -6
  100. package/dist/dts/internal/memory.d.ts.map +0 -1
  101. package/dist/dts/internal/shared.d.ts.map +0 -1
  102. package/dist/esm/Blocking.js +0 -46
  103. package/dist/esm/Blocking.js.map +0 -1
  104. package/dist/esm/Layer.js.map +0 -1
  105. package/dist/esm/Navigation.js +0 -238
  106. package/dist/esm/Navigation.js.map +0 -1
  107. package/dist/esm/index.js +0 -17
  108. package/dist/esm/index.js.map +0 -1
  109. package/dist/esm/internal/fromWindow.js.map +0 -1
  110. package/dist/esm/internal/memory.js +0 -56
  111. package/dist/esm/internal/memory.js.map +0 -1
  112. package/dist/esm/internal/shared.js.map +0 -1
  113. package/dist/esm/package.json +0 -4
@@ -1,22 +1,20 @@
1
- import * as Equivalence from "@effect/schema/Equivalence"
2
- import * as Context from "@typed/context"
3
- import { Window } from "@typed/dom/Window"
4
- import * as RefSubject from "@typed/fx/RefSubject"
5
- import { GetRandomValues, Uuid } from "@typed/id"
6
-
7
- import * as Effect from "effect/Effect"
8
- import type * as Fiber from "effect/Fiber"
9
- import * as Option from "effect/Option"
10
- import * as Runtime from "effect/Runtime"
11
- import * as Scope from "effect/Scope"
12
-
13
- import * as Schema from "@effect/schema/Schema"
14
- import * as Exit from "effect/Exit"
15
- import type * as Layer from "effect/Layer"
16
- import type { Commit } from "../Layer.js"
17
- import type { BeforeNavigationEvent, Destination, NavigationEvent, Transition } from "../Navigation.js"
18
- import { Navigation, NavigationError } from "../Navigation.js"
19
- import type { ModelAndIntent, PatchedState } from "./shared.js"
1
+ import { GetRandomValues, Uuid4 } from '@typed/id'
2
+ import * as LazyRef from '@typed/lazy-ref'
3
+ import { Schema } from 'effect'
4
+ import * as Context from 'effect/Context'
5
+ import * as Effect from 'effect/Effect'
6
+ import * as Exit from 'effect/Exit'
7
+ import type * as Fiber from 'effect/Fiber'
8
+ import * as Layer from 'effect/Layer'
9
+ import * as Option from 'effect/Option'
10
+ import * as Runtime from 'effect/Runtime'
11
+ import * as Scope from 'effect/Scope'
12
+ import type { Destination } from '../Destination.js'
13
+ import { NavigationError } from '../Error.js'
14
+ import type { NavigationEvent, TransitionEvent } from '../Event.js'
15
+ import type { Commit } from '../Layer.js'
16
+ import { Navigation } from '../Navigation.js'
17
+ import type { ModelAndIntent, PatchedState } from './shared.js'
20
18
  import {
21
19
  getOriginalState,
22
20
  getUrl,
@@ -24,169 +22,130 @@ import {
24
22
  makeDestination,
25
23
  makeHandlersState,
26
24
  NavigationState,
27
- setupFromModelAndIntent
28
- } from "./shared.js"
29
-
30
- /* eslint-disable @typescript-eslint/consistent-type-imports */
31
- type NativeNavigation = import("@virtualstate/navigation").Navigation
32
- type NativeEntry = import("@virtualstate/navigation").NavigationHistoryEntry
33
- type NativeEvent = import("@virtualstate/navigation").NavigationEventMap["navigate"]
34
- /* eslint-enable @typescript-eslint/consistent-type-imports */
35
-
36
- declare global {
37
- export interface Window {
38
- navigation?: NativeNavigation
39
- }
40
- }
25
+ setupFromModelAndIntent,
26
+ } from './shared.js'
41
27
 
42
- export const fromWindow: Layer.Layer<Navigation, never, Window> = Navigation.scoped(
43
- Window.withEffect((window) => {
44
- const getRandomValues = (length: number) => Effect.sync(() => window.crypto.getRandomValues(new Uint8Array(length)))
45
- return Effect.gen(function*() {
28
+ export const fromWindow: (window: Window) => Layer.Layer<Navigation, never, GetRandomValues> = (
29
+ window: Window,
30
+ ) =>
31
+ Layer.scoped(
32
+ Navigation,
33
+ Effect.gen(function* () {
34
+ const getRandomValues = yield* GetRandomValues
46
35
  const { run, runPromise } = yield* scopedRuntime<never>()
47
36
  const hasNativeNavigation = !!window.navigation
37
+ const base = getBaseHref(window)
48
38
  const modelAndIntent = yield* hasNativeNavigation
49
- ? setupWithNavigation(window.navigation!, runPromise)
50
- : setupWithHistory(window, (event) => run(handleHistoryEvent(event)))
39
+ ? setupWithNavigation(window.navigation, runPromise)
40
+ : setupWithHistory(window, base, (event) => run(handleHistoryEvent(event)))
51
41
 
52
42
  const navigation = setupFromModelAndIntent(
53
43
  modelAndIntent,
54
44
  window.location.origin,
55
- getBaseHref(window),
45
+ base,
56
46
  getRandomValues,
57
- hasNativeNavigation
58
- ? () => getNavigationState(window.navigation!)
59
- : undefined
47
+ hasNativeNavigation ? () => getNavigationState(window.navigation) : undefined,
60
48
  )
61
49
 
62
50
  return navigation
63
51
 
64
52
  function handleHistoryEvent(event: HistoryEvent) {
65
- return Effect.gen(function*() {
66
- if (event._tag === "PushState") {
67
- return yield* navigation.navigate(
68
- event.url,
69
- {},
70
- event.skipCommit
71
- )
72
- } else if (event._tag === "ReplaceState") {
53
+ return Effect.gen(function* () {
54
+ if (event._tag === 'PushState') {
55
+ return yield* navigation.navigate(event.url, {}, event.skipCommit)
56
+ } else if (event._tag === 'ReplaceState') {
73
57
  if (Option.isSome(event.url)) {
74
58
  return yield* navigation.navigate(
75
59
  event.url.value,
76
- { history: "replace", state: event.state },
77
- event.skipCommit
60
+ { history: 'replace', state: event.state },
61
+ event.skipCommit,
78
62
  )
79
63
  } else {
80
64
  return yield* navigation.updateCurrentEntry(event)
81
65
  }
82
- } else if (event._tag === "Traverse") {
66
+ } else if (event._tag === 'Traverse') {
83
67
  const { entries, index } = yield* modelAndIntent.state
84
- const toIndex = Math.min(
85
- Math.max(0, index + event.delta),
86
- entries.length - 1
87
- )
68
+ const toIndex = Math.min(Math.max(0, index + event.delta), entries.length - 1)
88
69
  const to = entries[toIndex]
89
70
 
90
- const result = yield* navigation.traverseTo(
91
- to.key,
92
- {},
93
- event.skipCommit
94
- )
71
+ const result = yield* navigation.traverseTo(to.key, {}, event.skipCommit)
95
72
 
96
73
  return result
97
74
  } else {
98
75
  yield* navigation.traverseTo(event.key, {}, event.skipCommit)
99
76
  return yield* navigation.updateCurrentEntry({
100
- state: event.state
77
+ state: event.state,
101
78
  })
102
79
  }
103
80
  })
104
81
  }
105
- }).pipe(GetRandomValues.provide(getRandomValues))
106
- })
107
- )
82
+ }),
83
+ )
108
84
 
109
85
  function getBaseHref(window: Window) {
110
- const base = window.document.querySelector("base")
111
- return base ? base.href : "/"
86
+ const base = window.document.querySelector('base')
87
+ return base ? base.href : '/'
112
88
  }
113
89
 
114
- const getNavigationState = (navigation: NativeNavigation): NavigationState => {
90
+ const getNavigationState = (navigation: globalThis.Navigation): NavigationState => {
115
91
  const entries = navigation.entries().map(nativeEntryToDestination)
116
- const { index } = navigation.currentEntry
92
+ // biome-ignore lint/style/noNonNullAssertion: <explanation>
93
+ const { index } = navigation.currentEntry!
117
94
 
118
95
  return {
119
96
  entries,
120
97
  index,
121
- transition: Option.none<Transition>()
98
+ transition: Option.none<TransitionEvent>(),
122
99
  }
123
100
  }
124
101
 
125
102
  function setupWithNavigation(
126
- navigation: NativeNavigation,
127
- runPromise: <E, A>(effect: Effect.Effect<A, E, Scope.Scope>) => Promise<A>
103
+ navigation: globalThis.Navigation,
104
+ runPromise: <E, A>(effect: Effect.Effect<A, E, Scope.Scope>) => Promise<A>,
128
105
  ): Effect.Effect<ModelAndIntent, never, Scope.Scope | GetRandomValues> {
129
- return Effect.gen(function*() {
130
- const state = yield* RefSubject.fromEffect(
106
+ return Effect.gen(function* () {
107
+ const state = yield* LazyRef.fromEffect(
131
108
  Effect.sync((): NavigationState => getNavigationState(navigation)),
132
109
  {
133
- eq: Equivalence.make(
134
- Schema.typeSchema(Schema.typeSchema(NavigationState))
135
- )
136
- }
110
+ eq: Schema.equivalence(Schema.typeSchema(NavigationState)),
111
+ },
137
112
  )
138
- const canGoBack = RefSubject.map(state, (s) => s.index > 0)
139
- const canGoForward = RefSubject.map(
140
- state,
141
- (s) => s.index < s.entries.length - 1
142
- )
143
- const { beforeHandlers, formDataHandlers, handlers } = yield* makeHandlersState()
144
- const commit: Commit = (to: Destination, event: BeforeNavigationEvent) =>
145
- Effect.gen(function*(_) {
113
+ const { beforeHandlers, handlers } = yield* makeHandlersState
114
+ const commit: Commit = (to: Destination, event: TransitionEvent) =>
115
+ Effect.gen(function* () {
146
116
  const { key, state, url } = to
147
117
  const { info, type } = event
148
118
 
149
- if (type === "push" || type === "replace") {
150
- yield* _(
151
- Effect.promise(
152
- () =>
153
- navigation.navigate(url.toString(), {
154
- history: type,
155
- state,
156
- info
157
- }).committed
158
- ),
159
- Effect.catchAllDefect((error) => Effect.fail(new NavigationError({ error })))
160
- )
161
- } else if (event.type === "reload") {
162
- yield* _(
163
- Effect.promise(() => navigation.reload({ state, info }).committed),
164
- Effect.catchAllDefect((error) => Effect.fail(new NavigationError({ error })))
119
+ if (type === 'push' || type === 'replace') {
120
+ yield* Effect.promise(
121
+ () =>
122
+ navigation.navigate(url.toString(), {
123
+ history: type,
124
+ state,
125
+ info,
126
+ }).committed,
165
127
  )
128
+ } else if (event.type === 'reload') {
129
+ yield* Effect.promise(() => navigation.reload({ state, info }).committed)
166
130
  } else {
167
- yield* _(
168
- Effect.promise(
169
- () => navigation.traverseTo(key, { info }).committed
170
- ),
171
- Effect.catchAllDefect((error) => Effect.fail(new NavigationError({ error })))
172
- )
131
+ yield* Effect.promise(() => navigation.traverseTo(key, { info }).committed)
173
132
  }
174
- })
133
+ }).pipe(Effect.catchAllDefect((cause) => new NavigationError({ cause })))
175
134
 
176
- const runHandlers = (native: NativeEvent) =>
177
- Effect.gen(function*() {
135
+ const runHandlers = (native: globalThis.NavigationEventMap['navigate']) =>
136
+ Effect.gen(function* () {
178
137
  const eventHandlers = yield* handlers
179
138
  const matches: Array<Effect.Effect<unknown>> = []
180
-
181
139
  const event: NavigationEvent = {
182
140
  type: native.navigationType,
183
- destination: nativeEntryToDestination(navigation.currentEntry),
184
- info: native.info
141
+ // biome-ignore lint/style/noNonNullAssertion: <explanation>
142
+ destination: nativeEntryToDestination(navigation.currentEntry!),
143
+ info: native.info,
185
144
  }
186
145
 
187
146
  for (const [handler, ctx] of eventHandlers) {
188
147
  const match = yield* Effect.provide(handler(event), ctx)
189
- if (Option.isSome(match)) {
148
+ if (match !== undefined && Option.isSome(match)) {
190
149
  matches.push(Effect.provide(match.value, ctx))
191
150
  }
192
151
  }
@@ -196,39 +155,40 @@ function setupWithNavigation(
196
155
  }
197
156
  })
198
157
 
199
- navigation.addEventListener("navigate", (ev) => {
158
+ navigation.addEventListener('navigate', (ev) => {
200
159
  if (shouldNotIntercept(ev)) return
201
160
 
202
161
  ev.intercept({
203
- handler: () => runPromise(runHandlers(ev))
162
+ handler: () => runPromise(runHandlers(ev)),
204
163
  })
205
164
  })
206
165
 
207
166
  return {
208
167
  state,
209
- canGoBack,
210
- canGoForward,
211
168
  beforeHandlers,
212
169
  handlers,
213
- formDataHandlers,
214
- commit
170
+ commit,
215
171
  } as const
216
172
  })
217
173
  }
218
174
 
219
175
  function nativeEntryToDestination(
220
- entry: Pick<NativeEntry, "id" | "key" | "url" | "getState" | "sameDocument">
176
+ entry: Pick<
177
+ globalThis.NavigationHistoryEntry,
178
+ 'id' | 'key' | 'url' | 'getState' | 'sameDocument'
179
+ >,
221
180
  ): Destination {
222
181
  return {
223
- id: Uuid(entry.id),
224
- key: Uuid(entry.key),
182
+ id: Uuid4.make(entry.id),
183
+ key: Uuid4.make(entry.key),
184
+ // biome-ignore lint/style/noNonNullAssertion: <explanation>
225
185
  url: new URL(entry.url!),
226
186
  state: entry.getState(),
227
- sameDocument: entry.sameDocument
187
+ sameDocument: entry.sameDocument,
228
188
  }
229
189
  }
230
190
 
231
- function shouldNotIntercept(navigationEvent: NativeEvent): boolean {
191
+ function shouldNotIntercept(navigationEvent: globalThis.NavigationEventMap['navigate']): boolean {
232
192
  return (
233
193
  !navigationEvent.canIntercept ||
234
194
  // If this is just a hashChange,
@@ -245,69 +205,54 @@ function shouldNotIntercept(navigationEvent: NativeEvent): boolean {
245
205
 
246
206
  function setupWithHistory(
247
207
  window: Window,
248
- onEvent: (event: HistoryEvent) => void
208
+ base: string,
209
+ onEvent: (event: HistoryEvent) => void,
249
210
  ): Effect.Effect<ModelAndIntent, never, GetRandomValues | Scope.Scope> {
250
- return Effect.gen(function*() {
211
+ return Effect.gen(function* () {
251
212
  const { location } = window
252
- const {
253
- getHistoryState,
254
- original: history,
255
- unpatch
256
- } = patchHistory(window, onEvent)
213
+ const { getHistoryState, original: history, unpatch } = patchHistory(window, onEvent, base)
257
214
 
258
215
  yield* Effect.addFinalizer(() => unpatch)
259
216
 
260
- const state = yield* RefSubject.fromEffect(
217
+ const state = yield* LazyRef.fromEffect(
261
218
  Effect.suspend(() =>
262
219
  Effect.map(
263
- makeDestination(
264
- new URL(location.href),
265
- getHistoryState(),
266
- location.origin
267
- ),
220
+ makeDestination(new URL(location.href), getHistoryState(), location.origin),
268
221
  (destination): NavigationState => ({
269
222
  entries: [destination],
270
223
  index: 0,
271
- transition: Option.none()
272
- })
273
- )
224
+ transition: Option.none(),
225
+ }),
226
+ ),
274
227
  ),
275
- { eq: Equivalence.make(Schema.typeSchema(NavigationState)) }
276
- )
277
- const canGoBack = RefSubject.map(state, (s) => s.index > 0)
278
- const canGoForward = RefSubject.map(
279
- state,
280
- (s) => s.index < s.entries.length - 1
228
+ { eq: Schema.equivalence(Schema.typeSchema(NavigationState)) },
281
229
  )
282
- const { beforeHandlers, formDataHandlers, handlers } = yield* makeHandlersState()
283
- const commit: Commit = (
284
- { id, key, state, url }: Destination,
285
- event: BeforeNavigationEvent
286
- ) =>
230
+ const { beforeHandlers, handlers } = yield* makeHandlersState
231
+ const commit: Commit = ({ id, key, state, url }: Destination, event: TransitionEvent) =>
287
232
  Effect.sync(() => {
288
233
  const { type } = event
289
234
 
290
- if (type === "push") {
235
+ if (type === 'push') {
291
236
  history.pushState(
292
237
  {
293
238
  __typed__navigation__id__: id,
294
239
  __typed__navigation__key__: key,
295
- __typed__navigation__state__: state
240
+ __typed__navigation__state__: state,
296
241
  },
297
- "",
298
- url
242
+ '',
243
+ url,
299
244
  )
300
- } else if (type === "replace") {
245
+ } else if (type === 'replace') {
301
246
  history.replaceState(
302
247
  {
303
248
  __typed__navigation__id__: id,
304
249
  __typed__navigation__key__: key,
305
- __typed__navigation__state__: state
250
+ __typed__navigation__state__: state,
306
251
  },
307
- "",
308
- url
252
+ '',
253
+ url,
309
254
  )
310
- } else if (event.type === "reload") {
255
+ } else if (event.type === 'reload') {
311
256
  location.reload()
312
257
  } else {
313
258
  history.go(event.delta)
@@ -316,63 +261,57 @@ function setupWithHistory(
316
261
  {
317
262
  __typed__navigation__id__: id,
318
263
  __typed__navigation__key__: key,
319
- __typed__navigation__state__: state
264
+ __typed__navigation__state__: state,
320
265
  },
321
- "",
322
- window.location.href
266
+ '',
267
+ window.location.href,
323
268
  )
324
269
  }
325
270
  })
326
271
 
327
272
  return {
328
273
  state,
329
- canGoBack,
330
- canGoForward,
331
274
  beforeHandlers,
332
275
  handlers,
333
- formDataHandlers,
334
- commit
276
+ commit,
335
277
  } satisfies ModelAndIntent
336
278
  })
337
279
  }
338
280
 
339
- type HistoryEvent =
340
- | PushStateEvent
341
- | ReplaceStateEvent
342
- | TraverseEvent
343
- | TraverseToEvent
281
+ type HistoryEvent = PushStateEvent | ReplaceStateEvent | TraverseEvent | TraverseToEvent
344
282
 
345
283
  type PushStateEvent = {
346
- _tag: "PushState"
284
+ _tag: 'PushState'
347
285
  state: unknown
348
286
  url: URL
349
287
  skipCommit: boolean
350
288
  }
351
289
  type ReplaceStateEvent = {
352
- _tag: "ReplaceState"
290
+ _tag: 'ReplaceState'
353
291
  state: unknown
354
292
  url: Option.Option<URL>
355
293
  skipCommit: boolean
356
294
  }
357
- type TraverseEvent = { _tag: "Traverse"; delta: number; skipCommit: boolean }
295
+ type TraverseEvent = { _tag: 'Traverse'; delta: number; skipCommit: boolean }
358
296
  type TraverseToEvent = {
359
- _tag: "TraverseTo"
360
- key: Uuid
297
+ _tag: 'TraverseTo'
298
+ key: Uuid4
361
299
  state: unknown
362
300
  skipCommit: boolean
363
301
  }
364
302
 
365
- function patchHistory(window: Window, onEvent: (event: HistoryEvent) => void) {
303
+ function patchHistory(window: Window, onEvent: (event: HistoryEvent) => void, base: string) {
366
304
  const { history, location } = window
367
- const stateDescriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(history), "state") ||
368
- Object.getOwnPropertyDescriptor(history, "state")
305
+ const stateDescriptor =
306
+ Object.getOwnPropertyDescriptor(Object.getPrototypeOf(history), 'state') ||
307
+ Object.getOwnPropertyDescriptor(history, 'state')
369
308
 
370
309
  const methods = {
371
310
  pushState: history.pushState.bind(history),
372
311
  replaceState: history.replaceState.bind(history),
373
312
  go: history.go.bind(history),
374
313
  back: history.back.bind(history),
375
- forward: history.forward.bind(history)
314
+ forward: history.forward.bind(history),
376
315
  }
377
316
  const getStateDescriptor = stateDescriptor?.get?.bind(history)
378
317
 
@@ -397,76 +336,76 @@ function patchHistory(window: Window, onEvent: (event: HistoryEvent) => void) {
397
336
  },
398
337
  replaceState(data, _, url) {
399
338
  return methods.replaceState(data, _, url?.toString())
400
- }
339
+ },
401
340
  }
402
341
 
403
342
  history.pushState = (state, _, url) => {
404
343
  if (url) {
405
344
  onEvent({
406
- _tag: "PushState",
345
+ _tag: 'PushState',
407
346
  state,
408
- url: getUrl(location.origin, url),
409
- skipCommit: false
347
+ url: getUrl(location.origin, url, base),
348
+ skipCommit: false,
410
349
  })
411
350
  } else {
412
351
  onEvent({
413
- _tag: "ReplaceState",
352
+ _tag: 'ReplaceState',
414
353
  state,
415
354
  url: Option.none(),
416
- skipCommit: false
355
+ skipCommit: false,
417
356
  })
418
357
  }
419
358
  }
420
359
  history.replaceState = (state, _, url) => {
421
360
  onEvent({
422
- _tag: "ReplaceState",
361
+ _tag: 'ReplaceState',
423
362
  state,
424
- url: url ? Option.some(getUrl(location.origin, url)) : Option.none(),
425
- skipCommit: false
363
+ url: url ? Option.some(getUrl(location.origin, url, base)) : Option.none(),
364
+ skipCommit: false,
426
365
  })
427
366
  }
428
367
  history.go = (delta) => {
429
368
  if (delta && delta !== 0) {
430
- onEvent({ _tag: "Traverse", delta, skipCommit: false })
369
+ onEvent({ _tag: 'Traverse', delta, skipCommit: false })
431
370
  }
432
371
  }
433
372
  history.back = () => {
434
- onEvent({ _tag: "Traverse", delta: -1, skipCommit: false })
373
+ onEvent({ _tag: 'Traverse', delta: -1, skipCommit: false })
435
374
  }
436
375
  history.forward = () => {
437
- onEvent({ _tag: "Traverse", delta: 1, skipCommit: false })
376
+ onEvent({ _tag: 'Traverse', delta: 1, skipCommit: false })
438
377
  }
439
378
 
440
379
  const onHashChange = (ev: HashChangeEvent) => {
441
380
  onEvent({
442
- _tag: "ReplaceState",
381
+ _tag: 'ReplaceState',
443
382
  state: history.state,
444
383
  url: Option.some(new URL(ev.newURL)),
445
- skipCommit: false
384
+ skipCommit: false,
446
385
  })
447
386
  }
448
387
 
449
- window.addEventListener("hashchange", onHashChange, { capture: true })
388
+ window.addEventListener('hashchange', onHashChange, { capture: true })
450
389
 
451
390
  const onPopState = (ev: PopStateEvent) => {
452
391
  if (isPatchedState(ev.state)) {
453
392
  onEvent({
454
- _tag: "TraverseTo",
393
+ _tag: 'TraverseTo',
455
394
  key: ev.state.__typed__navigation__key__,
456
395
  state: ev.state.__typed__navigation__state__,
457
- skipCommit: true
396
+ skipCommit: true,
458
397
  })
459
398
  } else {
460
399
  onEvent({
461
- _tag: "ReplaceState",
400
+ _tag: 'ReplaceState',
462
401
  state: ev.state,
463
402
  url: Option.some(new URL(location.href)),
464
- skipCommit: true
403
+ skipCommit: true,
465
404
  })
466
405
  }
467
406
  }
468
407
 
469
- window.addEventListener("popstate", onPopState, { capture: true })
408
+ window.addEventListener('popstate', onPopState, { capture: true })
470
409
 
471
410
  const unpatch = Effect.sync(() => {
472
411
  history.pushState = original.pushState
@@ -477,28 +416,28 @@ function patchHistory(window: Window, onEvent: (event: HistoryEvent) => void) {
477
416
 
478
417
  if (stateDescriptor) {
479
418
  try {
480
- Object.defineProperty(history, "state", stateDescriptor)
419
+ Object.defineProperty(history, 'state', stateDescriptor)
481
420
  } catch {
482
421
  // We tried, but it didn't work
483
422
  }
484
423
  }
485
424
 
486
- window.removeEventListener("hashchange", onHashChange)
487
- window.removeEventListener("popstate", onPopState)
425
+ window.removeEventListener('hashchange', onHashChange)
426
+ window.removeEventListener('popstate', onPopState)
488
427
  })
489
428
 
490
- Object.defineProperty(history, "state", {
429
+ Object.defineProperty(history, 'state', {
491
430
  get() {
492
- console.log("here")
493
431
  return getOriginalState(getStateDescriptor?.() ?? history.state)
494
432
  },
495
433
  set(value) {
496
- const { __typed__navigation__id__, __typed__navigation__key__ } = getStateDescriptor?.() ?? original.state
434
+ const { __typed__navigation__id__, __typed__navigation__key__ } =
435
+ getStateDescriptor?.() ?? original.state
497
436
 
498
437
  if (isPatchedState(value)) {
499
438
  // The setter is not actually modifying the history.state
500
439
  // We need to call the original replaceState to update the actual state
501
- original.replaceState.call(history, value, "", location.href)
440
+ original.replaceState.call(history, value, '', location.href)
502
441
  } else {
503
442
  // The setter is not actually modifying the history.state
504
443
  // We need to call the original replaceState to update the actual state
@@ -507,54 +446,44 @@ function patchHistory(window: Window, onEvent: (event: HistoryEvent) => void) {
507
446
  {
508
447
  __typed__navigation__id__,
509
448
  __typed__navigation__key__,
510
- __typed__navigation__state__: value
449
+ __typed__navigation__state__: value,
511
450
  } satisfies PatchedState,
512
- "",
513
- location.href
451
+ '',
452
+ location.href,
514
453
  )
515
454
  }
516
455
 
517
456
  return value
518
- }
457
+ },
519
458
  })
520
459
 
521
460
  return {
522
461
  getHistoryState,
523
462
  original,
524
463
  patched: history,
525
- unpatch
464
+ unpatch,
526
465
  } as const
527
466
  }
528
467
 
529
468
  type ScopedRuntime<R> = {
530
469
  readonly runtime: Runtime.Runtime<R | Scope.Scope>
531
470
  readonly scope: Scope.Scope
532
- readonly run: <E, A>(
533
- effect: Effect.Effect<A, E, R | Scope.Scope>
534
- ) => Fiber.RuntimeFiber<A, E>
535
- readonly runPromise: <E, A>(
536
- effect: Effect.Effect<A, E, R | Scope.Scope>
537
- ) => Promise<A>
471
+ readonly run: <E, A>(effect: Effect.Effect<A, E, R | Scope.Scope>) => Fiber.RuntimeFiber<A, E>
472
+ readonly runPromise: <E, A>(effect: Effect.Effect<A, E, R | Scope.Scope>) => Promise<A>
538
473
  }
539
474
 
540
- function scopedRuntime<R>(): Effect.Effect<
541
- ScopedRuntime<R>,
542
- never,
543
- R | Scope.Scope
544
- > {
475
+ function scopedRuntime<R>(): Effect.Effect<ScopedRuntime<R>, never, R | Scope.Scope> {
545
476
  return Effect.map(Effect.runtime<R | Scope.Scope>(), (runtime) => {
546
477
  const scope = Context.get(runtime.context, Scope.Scope)
547
478
  const runFork = Runtime.runFork(runtime)
548
- const runPromise = <E, A>(
549
- effect: Effect.Effect<A, E, R | Scope.Scope>
550
- ): Promise<A> =>
479
+ const runPromise = <E, A>(effect: Effect.Effect<A, E, R | Scope.Scope>): Promise<A> =>
551
480
  new Promise((resolve, reject) => {
552
481
  const fiber = runFork(effect, { scope })
553
482
  fiber.addObserver(
554
483
  Exit.match({
555
484
  onFailure: (cause) => reject(Runtime.makeFiberFailure(cause)),
556
- onSuccess: resolve
557
- })
485
+ onSuccess: resolve,
486
+ }),
558
487
  )
559
488
  })
560
489
 
@@ -562,7 +491,7 @@ function scopedRuntime<R>(): Effect.Effect<
562
491
  runtime,
563
492
  scope: Context.unsafeGet(runtime.context, Scope.Scope),
564
493
  run: (eff) => runFork(eff, { scope }),
565
- runPromise
494
+ runPromise,
566
495
  } as const
567
496
  })
568
497
  }