@typed/navigation 0.7.0 → 0.8.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.
package/src/Blocking.ts CHANGED
@@ -2,10 +2,14 @@
2
2
  * @since 1.0.0
3
3
  */
4
4
 
5
- import type * as Computed from "@typed/fx/Computed"
6
5
  import * as RefSubject from "@typed/fx/RefSubject"
7
- import type { Scope } from "effect"
8
- import { Deferred, Effect, Option } from "effect"
6
+
7
+ import * as Data from "effect/Data"
8
+ import * as Deferred from "effect/Deferred"
9
+ import * as Effect from "effect/Effect"
10
+ import * as Option from "effect/Option"
11
+
12
+ import type * as Scope from "effect/Scope"
9
13
  import type {
10
14
  BeforeNavigationEvent,
11
15
  CancelNavigation,
@@ -18,8 +22,8 @@ import { cancelNavigation, Navigation, redirectToPath } from "./Navigation.js"
18
22
  /**
19
23
  * @since 1.0.0
20
24
  */
21
- export interface BlockNavigation extends Computed.Computed<never, never, Option.Option<Blocking>> {
22
- readonly isBlocking: Computed.Computed<never, never, boolean>
25
+ export interface BlockNavigation extends RefSubject.Computed<never, never, Option.Option<Blocking>> {
26
+ readonly isBlocking: RefSubject.Computed<never, never, boolean>
23
27
  }
24
28
 
25
29
  /**
@@ -36,7 +40,7 @@ type InternalBlockState = Unblocked | Blocked
36
40
  type Unblocked = {
37
41
  readonly _tag: "Unblocked"
38
42
  }
39
- const Unblocked: Unblocked = { _tag: "Unblocked" }
43
+ const Unblocked: Unblocked = Data.struct({ _tag: "Unblocked" })
40
44
 
41
45
  type Blocked = {
42
46
  readonly _tag: "Blocked"
@@ -47,7 +51,7 @@ type Blocked = {
47
51
  const Blocked = (event: BeforeNavigationEvent) =>
48
52
  Effect.map(
49
53
  Deferred.make<RedirectError | CancelNavigation, void>(),
50
- (deferred): Blocked => ({ _tag: "Blocked", deferred, event })
54
+ (deferred): Blocked => Data.struct({ _tag: "Blocked", deferred, event })
51
55
  )
52
56
 
53
57
  /**
@@ -69,7 +73,7 @@ export const useBlockNavigation = <R = never>(
69
73
 
70
74
  yield* _(
71
75
  navigation.beforeNavigation<R, never>((event) =>
72
- blockState.modifyEffect((state) =>
76
+ RefSubject.modifyEffect(blockState, (state) =>
73
77
  Effect.gen(function*(_) {
74
78
  // Can't block twice
75
79
  if (state._tag === "Blocked") return [Option.none(), state] as const
@@ -84,14 +88,17 @@ export const useBlockNavigation = <R = never>(
84
88
  Option.some(Deferred.await(updated.deferred)),
85
89
  updated
86
90
  ] as const
87
- })
88
- )
91
+ }))
89
92
  )
90
93
  )
91
94
 
92
95
  const blockNavigation: BlockNavigation = Object.assign(
93
- blockState.map((s) => s._tag === "Blocked" ? Option.some(blockedToBlocking(navigation, s)) : Option.none()),
94
- { isBlocking: blockState.map((s) => s._tag === "Blocked") }
96
+ RefSubject.map(blockState, (s) => {
97
+ return s._tag === "Blocked" ? Option.some(blockedToBlocking(navigation, s)) : Option.none()
98
+ }),
99
+ {
100
+ isBlocking: RefSubject.map(blockState, (s) => s._tag === "Blocked")
101
+ }
95
102
  )
96
103
 
97
104
  return blockNavigation
package/src/Navigation.ts CHANGED
@@ -5,12 +5,13 @@
5
5
  import { ParseResult } from "@effect/schema"
6
6
  import * as Schema from "@effect/schema/Schema"
7
7
  import { Tagged } from "@typed/context"
8
- import * as Computed from "@typed/fx/Computed"
8
+ import * as RefSubject from "@typed/fx/RefSubject"
9
9
  import type { Uuid } from "@typed/id"
10
10
  import * as IdSchema from "@typed/id/Schema"
11
11
  import type { Option, Scope } from "effect"
12
- import { Data, Effect } from "effect"
13
12
 
13
+ import * as Data from "effect/Data"
14
+ import * as Effect from "effect/Effect"
14
15
  /**
15
16
  * @since 1.0.0
16
17
  */
@@ -19,15 +20,15 @@ export interface Navigation {
19
20
 
20
21
  readonly base: string
21
22
 
22
- readonly currentEntry: Computed.Computed<never, never, Destination>
23
+ readonly currentEntry: RefSubject.Computed<never, never, Destination>
23
24
 
24
- readonly entries: Computed.Computed<never, never, ReadonlyArray<Destination>>
25
+ readonly entries: RefSubject.Computed<never, never, ReadonlyArray<Destination>>
25
26
 
26
- readonly transition: Computed.Computed<never, never, Option.Option<Transition>>
27
+ readonly transition: RefSubject.Computed<never, never, Option.Option<Transition>>
27
28
 
28
- readonly canGoBack: Computed.Computed<never, never, boolean>
29
+ readonly canGoBack: RefSubject.Computed<never, never, boolean>
29
30
 
30
- readonly canGoForward: Computed.Computed<never, never, boolean>
31
+ readonly canGoForward: RefSubject.Computed<never, never, boolean>
31
32
 
32
33
  readonly navigate: (
33
34
  url: string | URL,
@@ -75,7 +76,7 @@ const urlSchema = Schema.string.pipe(
75
76
  try {
76
77
  return Effect.succeed(new URL(s))
77
78
  } catch {
78
- return Effect.fail(ParseResult.parseError([ParseResult.type(urlSchema_.ast, s, `Expected a URL`)]))
79
+ return Effect.fail(ParseResult.parseError(ParseResult.type(urlSchema_.ast, s, `Expected a URL`)))
79
80
  }
80
81
  }),
81
82
  (url) => Effect.succeed(url.toString())
@@ -324,7 +325,7 @@ export const reload: (
324
325
  /**
325
326
  * @since 1.0.0
326
327
  */
327
- export const CurrentEntry: Computed.Computed<Navigation, never, Destination> = Computed.fromTag(
328
+ export const CurrentEntry: RefSubject.Computed<Navigation, never, Destination> = RefSubject.computedFromTag(
328
329
  Navigation,
329
330
  (nav) => nav.currentEntry
330
331
  )
@@ -339,22 +340,24 @@ export function getCurrentPathFromUrl(location: Pick<URL, "pathname" | "search"
339
340
  /**
340
341
  * @since 1.0.0
341
342
  */
342
- export const CurrentPath: Computed.Computed<Navigation, never, string> = CurrentEntry.map((d) =>
343
- getCurrentPathFromUrl(d.url)
343
+ export const CurrentPath: RefSubject.Computed<Navigation, never, string> = RefSubject.map(
344
+ CurrentEntry,
345
+ (d) => getCurrentPathFromUrl(d.url)
344
346
  )
345
347
 
346
348
  /**
347
349
  * @since 1.0.0
348
350
  */
349
- export const CurrentEntries: Computed.Computed<Navigation, never, ReadonlyArray<Destination>> = Computed.fromTag(
350
- Navigation,
351
- (n) => n.entries
352
- )
351
+ export const CurrentEntries: RefSubject.Computed<Navigation, never, ReadonlyArray<Destination>> = RefSubject
352
+ .computedFromTag(
353
+ Navigation,
354
+ (n) => n.entries
355
+ )
353
356
 
354
357
  /**
355
358
  * @since 1.0.0
356
359
  */
357
- export const CanGoForward: Computed.Computed<Navigation, never, boolean> = Computed.fromTag(
360
+ export const CanGoForward: RefSubject.Computed<Navigation, never, boolean> = RefSubject.computedFromTag(
358
361
  Navigation,
359
362
  (n) => n.canGoForward
360
363
  )
@@ -362,7 +365,7 @@ export const CanGoForward: Computed.Computed<Navigation, never, boolean> = Compu
362
365
  /**
363
366
  * @since 1.0.0
364
367
  */
365
- export const CanGoBack: Computed.Computed<Navigation, never, boolean> = Computed.fromTag(
368
+ export const CanGoBack: RefSubject.Computed<Navigation, never, boolean> = RefSubject.computedFromTag(
366
369
  Navigation,
367
370
  (n) => n.canGoBack
368
371
  )
@@ -371,5 +374,8 @@ export const CanGoBack: Computed.Computed<Navigation, never, boolean> = Computed
371
374
  * @since 1.0.0
372
375
  */
373
376
  export function handleRedirect(error: RedirectError) {
374
- return navigate(error.path, error.options)
377
+ return navigate(error.path, {
378
+ history: "replace",
379
+ ...error.options
380
+ })
375
381
  }
@@ -1,27 +1,29 @@
1
+ import * as Equivalence from "@effect/schema/Equivalence"
1
2
  import { unsafeGet } from "@typed/context"
2
3
  import { Window } from "@typed/dom/Window"
3
- import type { Computed } from "@typed/fx/Computed"
4
4
  import * as RefSubject from "@typed/fx/RefSubject"
5
5
  import { GetRandomValues, Uuid } from "@typed/id"
6
- import { Effect, Exit, Fiber, Option, Runtime, Scope } from "effect"
7
- import type { Context, Layer } from "effect"
6
+
7
+ import * as Effect from "effect/Effect"
8
+ import * as Exit from "effect/Exit"
9
+ import type * as Fiber from "effect/Fiber"
10
+ import * as Option from "effect/Option"
11
+ import * as Runtime from "effect/Runtime"
12
+ import * as Scope from "effect/Scope"
13
+
14
+ import { Schema } from "@effect/schema"
15
+ import type { Layer } from "effect"
8
16
  import type { Commit } from "../Layer.js"
9
- import type {
10
- BeforeNavigationEvent,
11
- BeforeNavigationHandler,
12
- Destination,
13
- NavigationEvent,
14
- NavigationHandler,
15
- Transition
16
- } from "../Navigation.js"
17
+ import type { BeforeNavigationEvent, Destination, NavigationEvent, Transition } from "../Navigation.js"
17
18
  import { Navigation, NavigationError } from "../Navigation.js"
18
- import type { NavigationState } from "./shared.js"
19
+ import type { ModelAndIntent } from "./shared.js"
19
20
  import {
20
21
  getOriginalState,
21
22
  getUrl,
22
23
  isPatchedState,
23
24
  makeDestination,
24
25
  makeHandlersState,
26
+ NavigationState,
25
27
  setupFromModelAndIntent
26
28
  } from "./shared.js"
27
29
 
@@ -94,31 +96,6 @@ function getBaseHref(window: Window) {
94
96
  return base ? base.href : "/"
95
97
  }
96
98
 
97
- type ModelAndIntent = {
98
- readonly state: RefSubject.RefSubject<never, never, NavigationState>
99
- readonly canGoBack: Computed<
100
- never,
101
- never,
102
- boolean
103
- >
104
- readonly canGoForward: Computed<
105
- never,
106
- never,
107
- boolean
108
- >
109
- readonly beforeHandlers: RefSubject.RefSubject<
110
- never,
111
- never,
112
- Set<readonly [BeforeNavigationHandler<any, any>, Context.Context<any>]>
113
- >
114
- readonly handlers: RefSubject.RefSubject<
115
- never,
116
- never,
117
- Set<readonly [NavigationHandler<any, any>, Context.Context<any>]>
118
- >
119
- readonly commit: Commit
120
- }
121
-
122
99
  const getNavigationState = (navigation: NativeNavigation): NavigationState => {
123
100
  const entries = navigation.entries().map(nativeEntryToDestination)
124
101
  const { index } = navigation.currentEntry
@@ -141,11 +118,12 @@ function setupWithNavigation(
141
118
  return Effect.gen(function*(_) {
142
119
  const state = yield* _(
143
120
  RefSubject.fromEffect(
144
- Effect.sync((): NavigationState => getNavigationState(navigation))
121
+ Effect.sync((): NavigationState => getNavigationState(navigation)),
122
+ { eq: Equivalence.make(Schema.to(Schema.to(NavigationState))) }
145
123
  )
146
124
  )
147
- const canGoBack = state.map((s) => s.index > 0)
148
- const canGoForward = state.map((s) => s.index < s.entries.length - 1)
125
+ const canGoBack = RefSubject.map(state, (s) => s.index > 0)
126
+ const canGoForward = RefSubject.map(state, (s) => s.index < s.entries.length - 1)
149
127
  const { beforeHandlers, handlers } = yield* _(makeHandlersState())
150
128
  const commit: Commit = (to: Destination, event: BeforeNavigationEvent) =>
151
129
  Effect.gen(function*(_) {
@@ -264,11 +242,12 @@ function setupWithHistory(
264
242
  ),
265
243
  (destination): NavigationState => ({ entries: [destination], index: 0, transition: Option.none() })
266
244
  )
267
- )
245
+ ),
246
+ { eq: Equivalence.make(Schema.to(NavigationState)) }
268
247
  )
269
248
  )
270
- const canGoBack = state.map((s) => s.index > 0)
271
- const canGoForward = state.map((s) => s.index < s.entries.length - 1)
249
+ const canGoBack = RefSubject.map(state, (s) => s.index > 0)
250
+ const canGoForward = RefSubject.map(state, (s) => s.index < s.entries.length - 1)
272
251
  const { beforeHandlers, handlers } = yield* _(makeHandlersState())
273
252
  const commit: Commit = ({ id, key, state, url }: Destination, event: BeforeNavigationEvent) =>
274
253
  Effect.sync(() => {
@@ -292,7 +271,7 @@ function setupWithHistory(
292
271
  beforeHandlers,
293
272
  handlers,
294
273
  commit
295
- } as const
274
+ } as ModelAndIntent
296
275
  })
297
276
  }
298
277
 
@@ -438,11 +417,8 @@ function scopedRuntime<R>(): Effect.Effect<
438
417
  const runFork = Runtime.runFork(runtime)
439
418
 
440
419
  const run = <E, A>(effect: Effect.Effect<R | Scope.Scope, E, A>): Fiber.RuntimeFiber<E, A> => {
441
- const fiber: Fiber.RuntimeFiber<E, A> = Scope.addFinalizer(
442
- scope,
443
- Effect.suspend(() => Fiber.interrupt(fiber))
444
- ).pipe(
445
- Effect.zipRight(effect),
420
+ const fiber: Fiber.RuntimeFiber<E, A> = effect.pipe(
421
+ Scope.extend(scope),
446
422
  runFork
447
423
  )
448
424
 
@@ -1,11 +1,22 @@
1
+ import { Schema } from "@effect/schema"
2
+ import * as Equivalence from "@effect/schema/Equivalence"
1
3
  import * as RefSubject from "@typed/fx/RefSubject"
2
4
  import { GetRandomValues, getRandomValues } from "@typed/id"
3
- import { Effect, Option } from "effect"
4
- import type { Layer, Scope } from "effect"
5
+ import type { Layer } from "effect"
6
+ import * as Effect from "effect/Effect"
7
+ import * as Option from "effect/Option"
8
+ import type * as Scope from "effect/Scope"
5
9
  import type { Commit, InitialMemoryOptions, MemoryOptions } from "../Layer.js"
6
10
  import { Navigation } from "../Navigation.js"
7
- import type { ModelAndIntent, NavigationState } from "./shared.js"
8
- import { getOriginFromUrl, getUrl, makeDestination, makeHandlersState, setupFromModelAndIntent } from "./shared.js"
11
+ import type { ModelAndIntent } from "./shared.js"
12
+ import {
13
+ getOriginFromUrl,
14
+ getUrl,
15
+ makeDestination,
16
+ makeHandlersState,
17
+ NavigationState,
18
+ setupFromModelAndIntent
19
+ } from "./shared.js"
9
20
 
10
21
  export const memory = (options: MemoryOptions): Layer.Layer<never, never, Navigation> =>
11
22
  Navigation.scoped(
@@ -59,11 +70,12 @@ function setupMemory(
59
70
  index: options.currentIndex ?? options.entries.length - 1,
60
71
  transition: Option.none()
61
72
  }
62
- })
73
+ }),
74
+ { eq: Equivalence.make(Schema.to(NavigationState)) }
63
75
  )
64
76
  )
65
- const canGoBack = state.map((s) => s.index > 0)
66
- const canGoForward = state.map((s) => s.index < s.entries.length - 1)
77
+ const canGoBack = RefSubject.map(state, (s) => s.index > 0)
78
+ const canGoForward = RefSubject.map(state, (s) => s.index < s.entries.length - 1)
67
79
  const { beforeHandlers, handlers } = yield* _(makeHandlersState())
68
80
  const commit: Commit = options.commit ?? (() => Effect.unit)
69
81
 
@@ -1,11 +1,12 @@
1
1
  import { Schema } from "@effect/schema"
2
2
  import type * as Context from "@typed/context"
3
- import type { Computed } from "@typed/fx/Computed"
4
3
  import * as RefSubject from "@typed/fx/RefSubject"
5
4
  import type { Uuid } from "@typed/id"
6
5
  import { GetRandomValues, makeUuid } from "@typed/id"
7
- import type { Scope } from "effect"
8
- import { Effect, Either, Option } from "effect"
6
+ import * as Effect from "effect/Effect"
7
+ import * as Either from "effect/Either"
8
+ import * as Option from "effect/Option"
9
+ import type * as Scope from "effect/Scope"
9
10
  import type { Commit } from "../Layer.js"
10
11
  import type {
11
12
  BeforeNavigationEvent,
@@ -39,12 +40,12 @@ export const getUrl = (origin: string, urlOrPath: string | URL): URL => {
39
40
 
40
41
  export type ModelAndIntent = {
41
42
  readonly state: RefSubject.RefSubject<never, never, NavigationState>
42
- readonly canGoBack: Computed<
43
+ readonly canGoBack: RefSubject.Computed<
43
44
  never,
44
45
  never,
45
46
  boolean
46
47
  >
47
- readonly canGoForward: Computed<
48
+ readonly canGoForward: RefSubject.Computed<
48
49
  never,
49
50
  never,
50
51
  boolean
@@ -71,9 +72,9 @@ export function setupFromModelAndIntent(
71
72
  ) {
72
73
  const { beforeHandlers, canGoBack, canGoForward, commit, handlers, state } = modelAndIntent
73
74
 
74
- const entries = state.map((s) => s.entries)
75
- const currentEntry = state.map((s) => s.entries[s.index])
76
- const transition = state.map((s) => s.transition)
75
+ const entries = RefSubject.map(state, (s) => s.entries)
76
+ const currentEntry = RefSubject.map(state, (s) => s.entries[s.index])
77
+ const transition = RefSubject.map(state, (s) => s.transition)
77
78
 
78
79
  const runBeforeHandlers = (event: BeforeNavigationEvent) =>
79
80
  Effect.gen(function*(_) {
@@ -117,7 +118,7 @@ export function setupFromModelAndIntent(
117
118
  }
118
119
 
119
120
  if (matches.length > 0) {
120
- yield* _(Effect.all(matches))
121
+ yield* _(Effect.all(matches, { discard: true }))
121
122
  }
122
123
  })
123
124
 
@@ -210,7 +211,7 @@ export function setupFromModelAndIntent(
210
211
  })
211
212
 
212
213
  const navigate = (pathOrUrl: string | URL, options?: NavigateOptions, skipCommit: boolean = false) =>
213
- state.runUpdate((get, set) =>
214
+ state.runUpdates(({ get, set }) =>
214
215
  Effect.gen(function*(_) {
215
216
  const state = yield* _(get)
216
217
  const from = state.entries[state.index]
@@ -233,7 +234,7 @@ export function setupFromModelAndIntent(
233
234
  )
234
235
 
235
236
  const traverseTo = (key: Destination["key"], options?: { readonly info?: unknown }, skipCommit: boolean = false) =>
236
- state.runUpdate((get, set) =>
237
+ state.runUpdates(({ get, set }) =>
237
238
  Effect.gen(function*(_) {
238
239
  const state = yield* _(get)
239
240
  const { entries, index } = state
@@ -276,7 +277,7 @@ export function setupFromModelAndIntent(
276
277
  })
277
278
 
278
279
  const reload = (options?: { readonly info?: unknown }, skipCommit: boolean = false) =>
279
- state.runUpdate((get, set) =>
280
+ state.runUpdates(({ get, set }) =>
280
281
  Effect.gen(function*(_) {
281
282
  const { entries, index } = yield* _(state)
282
283
  const current = entries[index]
@@ -300,9 +301,9 @@ export function setupFromModelAndIntent(
300
301
  const entry = [handler, ctx] as const
301
302
 
302
303
  return Effect.zipRight(
303
- beforeHandlers.update((handlers) => new Set([...handlers, entry])),
304
+ RefSubject.update(beforeHandlers, (handlers) => new Set([...handlers, entry])),
304
305
  Effect.addFinalizer(() =>
305
- beforeHandlers.update((handlers) => {
306
+ RefSubject.update(beforeHandlers, (handlers) => {
306
307
  const updated = new Set(handlers)
307
308
  updated.delete(entry)
308
309
  return updated
@@ -318,9 +319,9 @@ export function setupFromModelAndIntent(
318
319
  const entry = [handler, ctx] as const
319
320
 
320
321
  return Effect.zipRight(
321
- handlers.update((handlers) => new Set([...handlers, entry])),
322
+ RefSubject.update(handlers, (handlers) => new Set([...handlers, entry])),
322
323
  Effect.addFinalizer(() =>
323
- handlers.update((handlers) => {
324
+ RefSubject.update(handlers, (handlers) => {
324
325
  const updated = new Set(handlers)
325
326
  updated.delete(entry)
326
327
  return updated
@@ -330,7 +331,7 @@ export function setupFromModelAndIntent(
330
331
  })
331
332
 
332
333
  const updateCurrentEntry = (options: { readonly state: unknown }) =>
333
- state.runUpdate((get, set) =>
334
+ state.runUpdates(({ get, set }) =>
334
335
  Effect.gen(function*(_) {
335
336
  const { entries, index } = yield* _(get)
336
337
  const current = entries[index]