@typed/navigation 0.17.0 → 0.18.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.
@@ -1,4 +1,5 @@
1
- import * as HttpClient from "@effect/platform/HttpClient"
1
+ import type { HttpClientError, HttpClientResponse } from "@effect/platform"
2
+ import { Headers, HttpClient, HttpClientRequest } from "@effect/platform"
2
3
 
3
4
  import { Schema } from "@effect/schema"
4
5
  import type * as Context from "@typed/context"
@@ -50,9 +51,13 @@ export type ModelAndIntent = {
50
51
  readonly beforeHandlers: RefSubject.RefSubject<
51
52
  Set<readonly [BeforeNavigationHandler<any, any>, Context.Context<any>]>
52
53
  >
53
- readonly handlers: RefSubject.RefSubject<Set<readonly [NavigationHandler<any, any>, Context.Context<any>]>>
54
+ readonly handlers: RefSubject.RefSubject<
55
+ Set<readonly [NavigationHandler<any, any>, Context.Context<any>]>
56
+ >
54
57
 
55
- readonly formDataHandlers: RefSubject.RefSubject<Set<readonly [FormDataHandler<any, any>, Context.Context<any>]>>
58
+ readonly formDataHandlers: RefSubject.RefSubject<
59
+ Set<readonly [FormDataHandler<any, any>, Context.Context<any>]>
60
+ >
56
61
 
57
62
  readonly commit: Commit
58
63
  }
@@ -66,7 +71,15 @@ export function setupFromModelAndIntent(
66
71
  getRandomValues: Context.Fn.FnOf<typeof GetRandomValues>,
67
72
  newNavigationState?: () => NavigationState
68
73
  ) {
69
- const { beforeHandlers, canGoBack, canGoForward, commit, formDataHandlers, handlers, state } = modelAndIntent
74
+ const {
75
+ beforeHandlers,
76
+ canGoBack,
77
+ canGoForward,
78
+ commit,
79
+ formDataHandlers,
80
+ handlers,
81
+ state
82
+ } = modelAndIntent
70
83
  const entries = RefSubject.map(state, (s) => s.entries)
71
84
  const currentEntry = RefSubject.map(state, (s) => s.entries[s.index])
72
85
  const transition = RefSubject.map(state, (s) => s.transition)
@@ -74,10 +87,15 @@ export function setupFromModelAndIntent(
74
87
  const runBeforeHandlers = (event: BeforeNavigationEvent) =>
75
88
  Effect.gen(function*() {
76
89
  const handlers = yield* beforeHandlers
77
- const matches: Array<Effect.Effect<unknown, RedirectError | CancelNavigation>> = []
90
+ const matches: Array<
91
+ Effect.Effect<unknown, RedirectError | CancelNavigation>
92
+ > = []
78
93
 
79
94
  for (const [handler, ctx] of handlers) {
80
- const exit = yield* handler(event).pipe(Effect.provide(ctx), Effect.either)
95
+ const exit = yield* handler(event).pipe(
96
+ Effect.provide(ctx),
97
+ Effect.either
98
+ )
81
99
  if (Either.isRight(exit)) {
82
100
  const match = exit.right
83
101
  if (Option.isSome(match)) {
@@ -120,18 +138,27 @@ export function setupFromModelAndIntent(
120
138
  const runFormDataHandlers = (
121
139
  event: FormDataEvent
122
140
  ): Effect.Effect<
123
- Either.Either<Option.Option<HttpClient.response.ClientResponse>, RedirectError | CancelNavigation>,
124
- NavigationError | HttpClient.error.HttpClientError,
125
- Scope.Scope | HttpClient.client.Client.Default
141
+ Either.Either<
142
+ Option.Option<HttpClientResponse.HttpClientResponse>,
143
+ RedirectError | CancelNavigation
144
+ >,
145
+ NavigationError | HttpClientError.HttpClientError,
146
+ Scope.Scope | HttpClient.HttpClient.Service
126
147
  > =>
127
148
  Effect.gen(function*() {
128
149
  const handlers = yield* formDataHandlers
129
150
  const matches: Array<
130
- Effect.Effect<Option.Option<HttpClient.response.ClientResponse>, RedirectError | CancelNavigation>
151
+ Effect.Effect<
152
+ Option.Option<HttpClientResponse.HttpClientResponse>,
153
+ RedirectError | CancelNavigation
154
+ >
131
155
  > = []
132
156
 
133
157
  for (const [handler, ctx] of handlers) {
134
- const exit = yield* handler(event).pipe(Effect.provide(ctx), Effect.either)
158
+ const exit = yield* handler(event).pipe(
159
+ Effect.provide(ctx),
160
+ Effect.either
161
+ )
135
162
  if (Either.isRight(exit)) {
136
163
  const match = exit.right
137
164
  if (Option.isSome(match)) {
@@ -153,7 +180,10 @@ export function setupFromModelAndIntent(
153
180
  }
154
181
  } else {
155
182
  // Only if there are 0 matches, we'll make a request to the server ourselves
156
- const response = yield* makeFormDataRequest(event, Option.getOrElse(event.action, () => event.from.url.href))
183
+ const response = yield* makeFormDataRequest(
184
+ event,
185
+ Option.getOrElse(event.action, () => event.from.url.href)
186
+ )
157
187
 
158
188
  return Either.right(Option.some(response))
159
189
  }
@@ -170,7 +200,10 @@ export function setupFromModelAndIntent(
170
200
  ): Effect.Effect<Destination, NavigationError> =>
171
201
  Effect.gen(function*() {
172
202
  let current = yield* get
173
- current = yield* set({ ...current, transition: Option.some(beforeEvent) })
203
+ current = yield* set({
204
+ ...current,
205
+ transition: Option.some(beforeEvent)
206
+ })
174
207
 
175
208
  if (!skipCommit) {
176
209
  const beforeError = yield* runBeforeHandlers(beforeEvent)
@@ -180,7 +213,9 @@ export function setupFromModelAndIntent(
180
213
  }
181
214
  }
182
215
 
183
- const to = isDestination(beforeEvent.to) ? beforeEvent.to : yield* upgradeProposedDestination(beforeEvent.to)
216
+ const to = isDestination(beforeEvent.to)
217
+ ? beforeEvent.to
218
+ : yield* upgradeProposedDestination(beforeEvent.to)
184
219
 
185
220
  if (!skipCommit) {
186
221
  yield* commit(to, beforeEvent)
@@ -215,7 +250,11 @@ export function setupFromModelAndIntent(
215
250
  const { delta } = beforeEvent
216
251
  const nextIndex = current.index + delta
217
252
 
218
- yield* set({ ...current, index: nextIndex, transition: Option.none() })
253
+ yield* set({
254
+ ...current,
255
+ index: nextIndex,
256
+ transition: Option.none()
257
+ })
219
258
  }
220
259
 
221
260
  yield* runHandlers(event)
@@ -249,16 +288,27 @@ export function setupFromModelAndIntent(
249
288
  }
250
289
  }).pipe(GetRandomValues.provide(getRandomValues))
251
290
 
252
- const navigate = (pathOrUrl: string | URL, options?: NavigateOptions, skipCommit: boolean = false) =>
291
+ const navigate = (
292
+ pathOrUrl: string | URL,
293
+ options?: NavigateOptions,
294
+ skipCommit: boolean = false
295
+ ) =>
253
296
  state.runUpdates(({ get, set }) =>
254
297
  Effect.gen(function*() {
255
298
  const state = yield* get
256
299
  const from = state.entries[state.index]
257
300
  const history = options?.history ?? "auto"
258
- const to = yield* makeOrUpdateDestination(state, getUrl(origin, pathOrUrl), options?.state, origin).pipe(
259
- GetRandomValues.provide(getRandomValues)
260
- )
261
- const type = history === "auto" ? from.key === to.key ? "replace" : "push" : history
301
+ const to = yield* makeOrUpdateDestination(
302
+ state,
303
+ getUrl(origin, pathOrUrl),
304
+ options?.state,
305
+ origin
306
+ ).pipe(GetRandomValues.provide(getRandomValues))
307
+ const type = history === "auto"
308
+ ? from.key === to.key
309
+ ? "replace"
310
+ : "push"
311
+ : history
262
312
  const event: BeforeNavigationEvent = {
263
313
  type,
264
314
  from,
@@ -271,7 +321,11 @@ export function setupFromModelAndIntent(
271
321
  })
272
322
  )
273
323
 
274
- const traverseTo = (key: Destination["key"], options?: { readonly info?: unknown }, skipCommit: boolean = false) =>
324
+ const traverseTo = (
325
+ key: Destination["key"],
326
+ options?: { readonly info?: unknown },
327
+ skipCommit: boolean = false
328
+ ) =>
275
329
  state.runUpdates(({ get, set }) =>
276
330
  Effect.gen(function*() {
277
331
  const state = yield* get
@@ -281,7 +335,9 @@ export function setupFromModelAndIntent(
281
335
 
282
336
  if (nextIndex === -1) return from
283
337
 
284
- const id = yield* makeUuid.pipe(GetRandomValues.provide(getRandomValues))
338
+ const id = yield* makeUuid.pipe(
339
+ GetRandomValues.provide(getRandomValues)
340
+ )
285
341
  const to = { ...entries[nextIndex], id }
286
342
  const delta = nextIndex - index
287
343
  const event: BeforeNavigationEvent = {
@@ -296,7 +352,10 @@ export function setupFromModelAndIntent(
296
352
  })
297
353
  )
298
354
 
299
- const back = (options?: { readonly info?: unknown }, skipCommit: boolean = false) =>
355
+ const back = (
356
+ options?: { readonly info?: unknown },
357
+ skipCommit: boolean = false
358
+ ) =>
300
359
  Effect.gen(function*() {
301
360
  const { entries, index } = yield* state
302
361
  if (index === 0) return entries[index]
@@ -305,7 +364,10 @@ export function setupFromModelAndIntent(
305
364
  return yield* traverseTo(key, options, skipCommit)
306
365
  })
307
366
 
308
- const forward = (options?: { readonly info?: unknown }, skipCommit: boolean = false) =>
367
+ const forward = (
368
+ options?: { readonly info?: unknown },
369
+ skipCommit: boolean = false
370
+ ) =>
309
371
  Effect.gen(function*() {
310
372
  const { entries, index } = yield* state
311
373
  if (index === entries.length - 1) return entries[index]
@@ -314,7 +376,10 @@ export function setupFromModelAndIntent(
314
376
  return yield* traverseTo(key, options, skipCommit)
315
377
  })
316
378
 
317
- const reload = (options?: { readonly info?: unknown }, skipCommit: boolean = false) =>
379
+ const reload = (
380
+ options?: { readonly info?: unknown },
381
+ skipCommit: boolean = false
382
+ ) =>
318
383
  state.runUpdates(({ get, set }) =>
319
384
  Effect.gen(function*() {
320
385
  const { entries, index } = yield* state
@@ -339,7 +404,10 @@ export function setupFromModelAndIntent(
339
404
  const entry = [handler, ctx] as const
340
405
 
341
406
  return Effect.zipRight(
342
- RefSubject.update(beforeHandlers, (handlers) => new Set([...handlers, entry])),
407
+ RefSubject.update(
408
+ beforeHandlers,
409
+ (handlers) => new Set([...handlers, entry])
410
+ ),
343
411
  Effect.addFinalizer(() =>
344
412
  RefSubject.update(beforeHandlers, (handlers) => {
345
413
  const updated = new Set(handlers)
@@ -357,7 +425,10 @@ export function setupFromModelAndIntent(
357
425
  const entry = [handler, ctx] as const
358
426
 
359
427
  return Effect.zipRight(
360
- RefSubject.update(handlers, (handlers) => new Set([...handlers, entry])),
428
+ RefSubject.update(
429
+ handlers,
430
+ (handlers) => new Set([...handlers, entry])
431
+ ),
361
432
  Effect.addFinalizer(() =>
362
433
  RefSubject.update(handlers, (handlers) => {
363
434
  const updated = new Set(handlers)
@@ -389,9 +460,9 @@ export function setupFromModelAndIntent(
389
460
  data: FormData,
390
461
  input?: Omit<FormInputFrom, "data">
391
462
  ): Effect.Effect<
392
- Option.Option<HttpClient.response.ClientResponse>,
393
- NavigationError | HttpClient.error.HttpClientError,
394
- Scope.Scope | HttpClient.client.Client.Default
463
+ Option.Option<HttpClientResponse.HttpClientResponse>,
464
+ NavigationError | HttpClientError.HttpClientError,
465
+ Scope.Scope | HttpClient.HttpClient.Service
395
466
  > =>
396
467
  state.runUpdates(({ get, set }) =>
397
468
  Effect.gen(function*() {
@@ -410,7 +481,7 @@ export function setupFromModelAndIntent(
410
481
 
411
482
  if (Either.isLeft(either)) {
412
483
  yield* handleError(either.left, get, set, 0)
413
- return Option.none<HttpClient.response.ClientResponse>()
484
+ return Option.none<HttpClientResponse.HttpClientResponse>()
414
485
  } else {
415
486
  if (Option.isNone(either.right)) {
416
487
  return either.right
@@ -420,7 +491,7 @@ export function setupFromModelAndIntent(
420
491
 
421
492
  // If it is a redirect
422
493
  if (REDIRECT_STATUS_CODES.has(response.status)) {
423
- const location = HttpClient.headers.get(response.headers, "location")
494
+ const location = Headers.get(response.headers, "location")
424
495
 
425
496
  // And we have a location header
426
497
  if (Option.isSome(location)) {
@@ -441,7 +512,10 @@ export function setupFromModelAndIntent(
441
512
  const entry = [handler, ctx] as const
442
513
 
443
514
  return Effect.zipRight(
444
- RefSubject.update(formDataHandlers, (handlers) => new Set([...handlers, entry])),
515
+ RefSubject.update(
516
+ formDataHandlers,
517
+ (handlers) => new Set([...handlers, entry])
518
+ ),
445
519
  Effect.addFinalizer(() =>
446
520
  RefSubject.update(formDataHandlers, (handlers) => {
447
521
  const updated = new Set(handlers)
@@ -503,7 +577,8 @@ export function makeOrUpdateDestination(
503
577
  ) {
504
578
  return Effect.gen(function*() {
505
579
  const current = navigationState.entries[navigationState.index]
506
- const isSameOriginAndPath = url.origin === current.url.origin && url.pathname === current.url.pathname
580
+ const isSameOriginAndPath = url.origin === current.url.origin &&
581
+ url.pathname === current.url.pathname
507
582
 
508
583
  if (isSameOriginAndPath) {
509
584
  const id = yield* makeUuid
@@ -526,10 +601,10 @@ export function makeDestination(url: URL, state: unknown, origin: string) {
526
601
  return Effect.gen(function*() {
527
602
  if (isPatchedState(state)) {
528
603
  const destination: Destination = {
529
- id: state.id,
530
- key: state.key,
604
+ id: state.__typed__navigation__id__,
605
+ key: state.__typed__navigation__key__,
531
606
  url,
532
- state: state.originalHistoryState,
607
+ state: state.__typed__navigation__state__,
533
608
  sameDocument: url.origin === origin
534
609
  }
535
610
 
@@ -569,19 +644,26 @@ export function upgradeProposedDestination(proposed: ProposedDestination) {
569
644
  }
570
645
 
571
646
  export type PatchedState = {
572
- readonly id: Uuid
573
- readonly key: Uuid
574
- readonly originalHistoryState: unknown
647
+ readonly __typed__navigation__id__: Uuid
648
+ readonly __typed__navigation__key__: Uuid
649
+ readonly __typed__navigation__state__: unknown
575
650
  }
576
651
 
577
652
  export function isPatchedState(state: unknown): state is PatchedState {
578
- if (state === null || !(typeof state === "object") || Array.isArray(state)) return false
579
- if ("id" in state && "key" in state && "originalHistoryState" in state) return true
653
+ if (state === null || !(typeof state === "object") || Array.isArray(state)) {
654
+ return false
655
+ }
656
+ if (
657
+ "__typed__navigation__id__" in state &&
658
+ "__typed__navigation__key__" in state
659
+ ) {
660
+ return true
661
+ }
580
662
  return false
581
663
  }
582
664
 
583
665
  export function getOriginalState(state: unknown) {
584
- if (isPatchedState(state)) return state.originalHistoryState
666
+ if (isPatchedState(state)) return state.__typed__navigation__state__
585
667
  return state
586
668
  }
587
669
 
@@ -597,7 +679,9 @@ export function getOriginFromUrl(url: string | URL) {
597
679
  }
598
680
  }
599
681
 
600
- export function isDestination(proposed: ProposedDestination): proposed is Destination {
682
+ export function isDestination(
683
+ proposed: ProposedDestination
684
+ ): proposed is Destination {
601
685
  return "id" in proposed && "key" in proposed
602
686
  }
603
687
 
@@ -606,15 +690,27 @@ const strictEqual = <A>(a: A, b: A) => a === b
606
690
  export function makeHandlersState() {
607
691
  return Effect.gen(function*() {
608
692
  const beforeHandlers = yield* RefSubject.fromEffect(
609
- Effect.sync(() => new Set<readonly [BeforeNavigationHandler<any, any>, Context.Context<any>]>()),
693
+ Effect.sync(
694
+ () =>
695
+ new Set<
696
+ readonly [BeforeNavigationHandler<any, any>, Context.Context<any>]
697
+ >()
698
+ ),
610
699
  { eq: strictEqual }
611
700
  )
612
701
  const handlers = yield* RefSubject.fromEffect(
613
- Effect.sync(() => new Set<readonly [NavigationHandler<any, any>, Context.Context<any>]>()),
702
+ Effect.sync(
703
+ () =>
704
+ new Set<
705
+ readonly [NavigationHandler<any, any>, Context.Context<any>]
706
+ >()
707
+ ),
614
708
  { eq: strictEqual }
615
709
  )
616
710
  const formDataHandlers = yield* RefSubject.fromEffect(
617
- Effect.sync(() => new Set<readonly [FormDataHandler<any, any>, Context.Context<any>]>()),
711
+ Effect.sync(
712
+ () => new Set<readonly [FormDataHandler<any, any>, Context.Context<any>]>()
713
+ ),
618
714
  { eq: strictEqual }
619
715
  )
620
716
 
@@ -627,21 +723,17 @@ export function makeHandlersState() {
627
723
  }
628
724
 
629
725
  function makeFormDataRequest(event: FormDataEvent, url: string) {
630
- const headers = new Headers()
726
+ const headers = new globalThis.Headers()
631
727
 
632
728
  if (Option.isSome(event.encoding)) {
633
729
  headers.set("Content-Type", event.encoding.value)
634
730
  }
635
731
  const method = Option.getOrElse(event.method, () => "POST")
636
732
 
637
- return Effect.flatMap(
638
- HttpClient.client.Client,
639
- (client) =>
640
- client(
641
- HttpClient.request.make(method as "POST")(url, {
642
- headers: HttpClient.headers.fromInput(headers),
643
- body: HttpClient.body.formData(event.data)
644
- })
645
- )
646
- )
733
+ return Effect.flatMap(HttpClient.HttpClient, (client) =>
734
+ client.execute(
735
+ HttpClientRequest.make(method as "POST")(url, {
736
+ headers: Headers.fromInput(headers)
737
+ }).pipe(HttpClientRequest.bodyFormData(event.data))
738
+ ))
647
739
  }