@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.
- package/dist/cjs/Blocking.js +5 -5
- package/dist/cjs/Blocking.js.map +1 -1
- package/dist/cjs/Layer.js +2 -2
- package/dist/cjs/Layer.js.map +1 -1
- package/dist/cjs/Navigation.js +21 -18
- package/dist/cjs/Navigation.js.map +1 -1
- package/dist/cjs/internal/fromWindow.js +60 -45
- package/dist/cjs/internal/fromWindow.js.map +1 -1
- package/dist/cjs/internal/memory.js +4 -4
- package/dist/cjs/internal/memory.js.map +1 -1
- package/dist/cjs/internal/shared.js +20 -17
- package/dist/cjs/internal/shared.js.map +1 -1
- package/dist/dts/Blocking.d.ts.map +1 -1
- package/dist/dts/Navigation.d.ts +21 -32
- package/dist/dts/Navigation.d.ts.map +1 -1
- package/dist/dts/internal/fromWindow.d.ts.map +1 -1
- package/dist/dts/internal/memory.d.ts.map +1 -1
- package/dist/dts/internal/shared.d.ts +11 -14
- package/dist/dts/internal/shared.d.ts.map +1 -1
- package/dist/esm/Navigation.js +8 -7
- package/dist/esm/Navigation.js.map +1 -1
- package/dist/esm/internal/fromWindow.js +99 -40
- package/dist/esm/internal/fromWindow.js.map +1 -1
- package/dist/esm/internal/shared.js +34 -18
- package/dist/esm/internal/shared.js.map +1 -1
- package/package.json +9 -8
- package/src/Navigation.ts +33 -31
- package/src/internal/fromWindow.ts +205 -64
- package/src/internal/shared.ts +150 -58
package/src/internal/shared.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
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<
|
|
54
|
+
readonly handlers: RefSubject.RefSubject<
|
|
55
|
+
Set<readonly [NavigationHandler<any, any>, Context.Context<any>]>
|
|
56
|
+
>
|
|
54
57
|
|
|
55
|
-
readonly formDataHandlers: RefSubject.RefSubject<
|
|
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 {
|
|
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<
|
|
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(
|
|
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<
|
|
124
|
-
|
|
125
|
-
|
|
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<
|
|
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(
|
|
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(
|
|
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({
|
|
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)
|
|
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({
|
|
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 = (
|
|
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(
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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 = (
|
|
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(
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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(
|
|
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(
|
|
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<
|
|
393
|
-
NavigationError |
|
|
394
|
-
Scope.Scope | HttpClient.
|
|
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<
|
|
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 =
|
|
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(
|
|
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 &&
|
|
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.
|
|
530
|
-
key: state.
|
|
604
|
+
id: state.__typed__navigation__id__,
|
|
605
|
+
key: state.__typed__navigation__key__,
|
|
531
606
|
url,
|
|
532
|
-
state: state.
|
|
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
|
|
573
|
-
readonly
|
|
574
|
-
readonly
|
|
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))
|
|
579
|
-
|
|
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.
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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
|
}
|