@typed/navigation 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 (186) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/dist/cjs/Blocking.js +53 -0
  4. package/dist/cjs/Blocking.js.map +1 -0
  5. package/dist/cjs/Layer.js +27 -0
  6. package/dist/cjs/Layer.js.map +1 -0
  7. package/dist/cjs/Navigation.js +184 -62
  8. package/dist/cjs/Navigation.js.map +1 -1
  9. package/dist/cjs/index.js +36 -17
  10. package/dist/cjs/index.js.map +1 -1
  11. package/dist/cjs/internal/fromWindow.js +376 -0
  12. package/dist/cjs/internal/fromWindow.js.map +1 -0
  13. package/dist/cjs/internal/memory.js +67 -0
  14. package/dist/cjs/internal/memory.js.map +1 -0
  15. package/dist/cjs/internal/shared.js +403 -0
  16. package/dist/cjs/internal/shared.js.map +1 -0
  17. package/dist/dts/Blocking.d.ts +33 -0
  18. package/dist/dts/Blocking.d.ts.map +1 -0
  19. package/dist/dts/Layer.d.ts +44 -0
  20. package/dist/dts/Layer.d.ts.map +1 -0
  21. package/dist/dts/Navigation.d.ts +377 -0
  22. package/dist/dts/Navigation.d.ts.map +1 -0
  23. package/dist/dts/index.d.ts +17 -0
  24. package/dist/dts/index.d.ts.map +1 -0
  25. package/dist/dts/internal/fromWindow.d.ts +12 -0
  26. package/dist/dts/internal/fromWindow.d.ts.map +1 -0
  27. package/dist/dts/internal/memory.d.ts +6 -0
  28. package/dist/dts/internal/memory.d.ts.map +1 -0
  29. package/dist/dts/internal/shared.d.ts +139 -0
  30. package/dist/dts/internal/shared.d.ts.map +1 -0
  31. package/dist/esm/Blocking.js +39 -0
  32. package/dist/esm/Blocking.js.map +1 -0
  33. package/dist/esm/Layer.js +18 -0
  34. package/dist/esm/Layer.js.map +1 -0
  35. package/dist/esm/Navigation.js +166 -0
  36. package/dist/esm/Navigation.js.map +1 -0
  37. package/dist/esm/index.js +17 -0
  38. package/dist/esm/index.js.map +1 -0
  39. package/dist/esm/internal/fromWindow.js +273 -0
  40. package/dist/esm/internal/fromWindow.js.map +1 -0
  41. package/dist/esm/internal/memory.js +54 -0
  42. package/dist/esm/internal/memory.js.map +1 -0
  43. package/dist/esm/internal/shared.js +336 -0
  44. package/dist/esm/internal/shared.js.map +1 -0
  45. package/dist/esm/package.json +4 -0
  46. package/package.json +52 -32
  47. package/src/Blocking.ts +102 -0
  48. package/src/Layer.ts +53 -0
  49. package/src/Navigation.ts +342 -159
  50. package/src/index.ts +17 -3
  51. package/src/internal/fromWindow.ts +421 -0
  52. package/src/internal/memory.ts +79 -0
  53. package/src/internal/shared.ts +514 -0
  54. package/dist/DOM.d.ts +0 -12
  55. package/dist/DOM.d.ts.map +0 -1
  56. package/dist/DOM.js +0 -87
  57. package/dist/DOM.js.map +0 -1
  58. package/dist/Memory.d.ts +0 -10
  59. package/dist/Memory.d.ts.map +0 -1
  60. package/dist/Memory.js +0 -55
  61. package/dist/Memory.js.map +0 -1
  62. package/dist/Navigation.d.ts +0 -116
  63. package/dist/Navigation.d.ts.map +0 -1
  64. package/dist/Navigation.js +0 -36
  65. package/dist/Navigation.js.map +0 -1
  66. package/dist/_makeServerWindow.d.ts +0 -16
  67. package/dist/_makeServerWindow.d.ts.map +0 -1
  68. package/dist/_makeServerWindow.js +0 -9
  69. package/dist/_makeServerWindow.js.map +0 -1
  70. package/dist/cjs/DOM.d.ts +0 -12
  71. package/dist/cjs/DOM.d.ts.map +0 -1
  72. package/dist/cjs/DOM.js +0 -116
  73. package/dist/cjs/DOM.js.map +0 -1
  74. package/dist/cjs/Memory.d.ts +0 -10
  75. package/dist/cjs/Memory.d.ts.map +0 -1
  76. package/dist/cjs/Memory.js +0 -82
  77. package/dist/cjs/Memory.js.map +0 -1
  78. package/dist/cjs/Navigation.d.ts +0 -116
  79. package/dist/cjs/Navigation.d.ts.map +0 -1
  80. package/dist/cjs/_makeServerWindow.d.ts +0 -16
  81. package/dist/cjs/_makeServerWindow.d.ts.map +0 -1
  82. package/dist/cjs/_makeServerWindow.js +0 -36
  83. package/dist/cjs/_makeServerWindow.js.map +0 -1
  84. package/dist/cjs/constant.d.ts +0 -2
  85. package/dist/cjs/constant.d.ts.map +0 -1
  86. package/dist/cjs/constant.js +0 -30
  87. package/dist/cjs/constant.js.map +0 -1
  88. package/dist/cjs/dom-intent.d.ts +0 -28
  89. package/dist/cjs/dom-intent.d.ts.map +0 -1
  90. package/dist/cjs/dom-intent.js +0 -172
  91. package/dist/cjs/dom-intent.js.map +0 -1
  92. package/dist/cjs/history.d.ts +0 -31
  93. package/dist/cjs/history.d.ts.map +0 -1
  94. package/dist/cjs/history.js +0 -131
  95. package/dist/cjs/history.js.map +0 -1
  96. package/dist/cjs/index.d.ts +0 -4
  97. package/dist/cjs/index.d.ts.map +0 -1
  98. package/dist/cjs/json.d.ts +0 -13
  99. package/dist/cjs/json.d.ts.map +0 -1
  100. package/dist/cjs/json.js +0 -24
  101. package/dist/cjs/json.js.map +0 -1
  102. package/dist/cjs/memory-intent.d.ts +0 -27
  103. package/dist/cjs/memory-intent.d.ts.map +0 -1
  104. package/dist/cjs/memory-intent.js +0 -156
  105. package/dist/cjs/memory-intent.js.map +0 -1
  106. package/dist/cjs/model.d.ts +0 -22
  107. package/dist/cjs/model.d.ts.map +0 -1
  108. package/dist/cjs/model.js +0 -48
  109. package/dist/cjs/model.js.map +0 -1
  110. package/dist/cjs/shared-intent.d.ts +0 -14
  111. package/dist/cjs/shared-intent.d.ts.map +0 -1
  112. package/dist/cjs/shared-intent.js +0 -82
  113. package/dist/cjs/shared-intent.js.map +0 -1
  114. package/dist/cjs/storage.d.ts +0 -19
  115. package/dist/cjs/storage.d.ts.map +0 -1
  116. package/dist/cjs/storage.js +0 -101
  117. package/dist/cjs/storage.js.map +0 -1
  118. package/dist/cjs/util.d.ts +0 -5
  119. package/dist/cjs/util.d.ts.map +0 -1
  120. package/dist/cjs/util.js +0 -39
  121. package/dist/cjs/util.js.map +0 -1
  122. package/dist/constant.d.ts +0 -2
  123. package/dist/constant.d.ts.map +0 -1
  124. package/dist/constant.js +0 -4
  125. package/dist/constant.js.map +0 -1
  126. package/dist/dom-intent.d.ts +0 -28
  127. package/dist/dom-intent.d.ts.map +0 -1
  128. package/dist/dom-intent.js +0 -140
  129. package/dist/dom-intent.js.map +0 -1
  130. package/dist/history.d.ts +0 -31
  131. package/dist/history.d.ts.map +0 -1
  132. package/dist/history.js +0 -104
  133. package/dist/history.js.map +0 -1
  134. package/dist/index.d.ts +0 -4
  135. package/dist/index.d.ts.map +0 -1
  136. package/dist/index.js +0 -4
  137. package/dist/index.js.map +0 -1
  138. package/dist/intent.d.ts +0 -31
  139. package/dist/intent.d.ts.map +0 -1
  140. package/dist/intent.js +0 -157
  141. package/dist/intent.js.map +0 -1
  142. package/dist/json.d.ts +0 -13
  143. package/dist/json.d.ts.map +0 -1
  144. package/dist/json.js +0 -17
  145. package/dist/json.js.map +0 -1
  146. package/dist/memory-intent.d.ts +0 -27
  147. package/dist/memory-intent.d.ts.map +0 -1
  148. package/dist/memory-intent.js +0 -124
  149. package/dist/memory-intent.js.map +0 -1
  150. package/dist/model.d.ts +0 -22
  151. package/dist/model.d.ts.map +0 -1
  152. package/dist/model.js +0 -21
  153. package/dist/model.js.map +0 -1
  154. package/dist/shared-intent.d.ts +0 -14
  155. package/dist/shared-intent.d.ts.map +0 -1
  156. package/dist/shared-intent.js +0 -51
  157. package/dist/shared-intent.js.map +0 -1
  158. package/dist/storage.d.ts +0 -19
  159. package/dist/storage.d.ts.map +0 -1
  160. package/dist/storage.js +0 -73
  161. package/dist/storage.js.map +0 -1
  162. package/dist/tsconfig.cjs.build.tsbuildinfo +0 -1
  163. package/dist/util.d.ts +0 -5
  164. package/dist/util.d.ts.map +0 -1
  165. package/dist/util.js +0 -12
  166. package/dist/util.js.map +0 -1
  167. package/eslintrc.json +0 -3
  168. package/project.json +0 -46
  169. package/src/DOM.test.ts +0 -699
  170. package/src/DOM.ts +0 -163
  171. package/src/Memory.test.ts +0 -464
  172. package/src/Memory.ts +0 -102
  173. package/src/_makeServerWindow.ts +0 -28
  174. package/src/dom-intent.ts +0 -268
  175. package/src/history.ts +0 -165
  176. package/src/json.ts +0 -31
  177. package/src/memory-intent.ts +0 -224
  178. package/src/model.ts +0 -54
  179. package/src/shared-intent.ts +0 -117
  180. package/src/storage.ts +0 -101
  181. package/src/util.ts +0 -20
  182. package/tsconfig.build.json +0 -4
  183. package/tsconfig.build.tsbuildinfo +0 -1
  184. package/tsconfig.cjs.build.json +0 -22
  185. package/tsconfig.json +0 -27
  186. package/vite.config.mjs +0 -3
@@ -0,0 +1,514 @@
1
+ import { Schema } from "@effect/schema"
2
+ import type * as Context from "@typed/context"
3
+ import type { Computed } from "@typed/fx/Computed"
4
+ import * as RefSubject from "@typed/fx/RefSubject"
5
+ import type { Uuid } from "@typed/id"
6
+ import { GetRandomValues, makeUuid } from "@typed/id"
7
+ import type { Scope } from "effect"
8
+ import { Effect, Either, Option } from "effect"
9
+ import type { Commit } from "../Layer"
10
+ import type {
11
+ BeforeNavigationEvent,
12
+ BeforeNavigationHandler,
13
+ CancelNavigation,
14
+ NavigateOptions,
15
+ Navigation,
16
+ NavigationError,
17
+ NavigationEvent,
18
+ NavigationHandler,
19
+ ProposedDestination,
20
+ RedirectError
21
+ } from "../Navigation"
22
+ import { Destination, Transition } from "../Navigation"
23
+
24
+ export type NavigationState = {
25
+ readonly entries: ReadonlyArray<Destination>
26
+ readonly index: number
27
+ readonly transition: Option.Option<Transition>
28
+ }
29
+
30
+ export const NavigationState = Schema.struct({
31
+ entries: Schema.array(Destination),
32
+ index: Schema.number,
33
+ transition: Schema.optionFromNullable(Transition)
34
+ })
35
+
36
+ export const getUrl = (origin: string, urlOrPath: string | URL): URL => {
37
+ return typeof urlOrPath === "string" ? new URL(urlOrPath, origin) : urlOrPath
38
+ }
39
+
40
+ export type ModelAndIntent = {
41
+ readonly state: RefSubject.RefSubject<never, never, NavigationState>
42
+ readonly canGoBack: Computed<
43
+ never,
44
+ never,
45
+ boolean
46
+ >
47
+ readonly canGoForward: Computed<
48
+ never,
49
+ never,
50
+ boolean
51
+ >
52
+ readonly beforeHandlers: RefSubject.RefSubject<
53
+ never,
54
+ never,
55
+ Set<readonly [BeforeNavigationHandler<any, any>, Context.Context<any>]>
56
+ >
57
+ readonly handlers: RefSubject.RefSubject<
58
+ never,
59
+ never,
60
+ Set<readonly [NavigationHandler<any, any>, Context.Context<any>]>
61
+ >
62
+ readonly commit: Commit
63
+ }
64
+
65
+ export function setupFromModelAndIntent(
66
+ modelAndIntent: ModelAndIntent,
67
+ origin: string,
68
+ base: string,
69
+ getRandomValues: Context.Fn.FnOf<typeof GetRandomValues>,
70
+ newNavigationState?: () => NavigationState
71
+ ) {
72
+ const { beforeHandlers, canGoBack, canGoForward, commit, handlers, state } = modelAndIntent
73
+
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)
77
+
78
+ const runBeforeHandlers = (event: BeforeNavigationEvent) =>
79
+ Effect.gen(function*(_) {
80
+ const handlers = yield* _(beforeHandlers)
81
+ const matches: Array<Effect.Effect<never, RedirectError | CancelNavigation, unknown>> = []
82
+
83
+ for (const [handler, ctx] of handlers) {
84
+ const exit = yield* _(handler(event), Effect.provide(ctx), Effect.either)
85
+ if (Either.isRight(exit)) {
86
+ const match = exit.right
87
+ if (Option.isSome(match)) {
88
+ matches.push(Effect.provide(match.value, ctx))
89
+ }
90
+ } else {
91
+ return Option.some(exit.left)
92
+ }
93
+ }
94
+
95
+ if (matches.length > 0) {
96
+ for (const match of matches) {
97
+ const exit = yield* _(match, Effect.either)
98
+ if (Either.isLeft(exit)) {
99
+ return Option.some(exit.left)
100
+ }
101
+ }
102
+ }
103
+
104
+ return Option.none<RedirectError | CancelNavigation>()
105
+ })
106
+
107
+ const runHandlers = (event: NavigationEvent) =>
108
+ Effect.gen(function*(_) {
109
+ const eventHandlers = yield* _(handlers)
110
+ const matches: Array<Effect.Effect<never, never, unknown>> = []
111
+
112
+ for (const [handler, ctx] of eventHandlers) {
113
+ const match = yield* _(handler(event), Effect.provide(ctx))
114
+ if (Option.isSome(match)) {
115
+ matches.push(Effect.provide(match.value, ctx))
116
+ }
117
+ }
118
+
119
+ if (matches.length > 0) {
120
+ yield* _(Effect.all(matches))
121
+ }
122
+ })
123
+
124
+ const runNavigationEvent = (
125
+ beforeEvent: BeforeNavigationEvent,
126
+ get: Effect.Effect<never, never, NavigationState>,
127
+ set: (a: NavigationState) => Effect.Effect<never, never, NavigationState>,
128
+ depth: number,
129
+ skipCommit: boolean = false
130
+ ): Effect.Effect<never, NavigationError, Destination> =>
131
+ Effect.gen(function*(_) {
132
+ let current = yield* _(get)
133
+ current = yield* _(set({ ...current, transition: Option.some(beforeEvent) }))
134
+
135
+ if (!skipCommit) {
136
+ const beforeError = yield* _(runBeforeHandlers(beforeEvent))
137
+
138
+ if (Option.isSome(beforeError)) {
139
+ return yield* _(handleError(beforeError.value, get, set, depth))
140
+ }
141
+ }
142
+
143
+ const to = isDestination(beforeEvent.to) ? beforeEvent.to : yield* _(upgradeProposedDestination(beforeEvent.to))
144
+
145
+ if (!skipCommit) {
146
+ yield* _(commit(to, beforeEvent))
147
+ }
148
+
149
+ if (newNavigationState) {
150
+ const { entries, index } = yield* _(set(newNavigationState()))
151
+
152
+ return entries[index]
153
+ } else {
154
+ const event: NavigationEvent = {
155
+ type: beforeEvent.type,
156
+ info: beforeEvent.info,
157
+ destination: to
158
+ }
159
+
160
+ if (beforeEvent.type === "push") {
161
+ const index = current.index + 1
162
+ const entries = current.entries.slice(0, index).concat([to])
163
+
164
+ yield* _(set({ entries, index, transition: Option.none() }))
165
+ } else if (beforeEvent.type === "replace") {
166
+ const index = current.index
167
+ const before = current.entries.slice(0, index)
168
+ const after = current.entries.slice(index + 1)
169
+ const entries = [...before, to, ...after]
170
+
171
+ yield* _(set({ entries, index, transition: Option.none() }))
172
+ } else if (beforeEvent.type === "reload") {
173
+ yield* _(set({ ...current, transition: Option.none() }))
174
+ } else {
175
+ const { delta } = beforeEvent
176
+ const nextIndex = current.index + delta
177
+
178
+ yield* _(set({ ...current, index: nextIndex, transition: Option.none() }))
179
+ }
180
+
181
+ yield* _(runHandlers(event))
182
+ }
183
+
184
+ return to
185
+ }).pipe(GetRandomValues.provide(getRandomValues))
186
+
187
+ const handleError = (
188
+ error: RedirectError | CancelNavigation,
189
+ get: Effect.Effect<never, never, NavigationState>,
190
+ set: (a: NavigationState) => Effect.Effect<never, never, NavigationState>,
191
+ depth: number
192
+ ) =>
193
+ Effect.gen(function*(_) {
194
+ if (depth >= 25) {
195
+ return yield* _(Effect.dieMessage(`Redirect loop detected.`))
196
+ }
197
+
198
+ const { entries, index } = yield* _(get)
199
+ const from = entries[index]
200
+
201
+ if (error._tag === "CancelNavigation") {
202
+ yield* _(set({ entries, index, transition: Option.none() }))
203
+
204
+ return from
205
+ } else {
206
+ const event = yield* _(makeRedirectEvent(origin, error, from))
207
+
208
+ return yield* _(runNavigationEvent(event, get, set, depth + 1))
209
+ }
210
+ })
211
+
212
+ const navigate = (pathOrUrl: string | URL, options?: NavigateOptions, skipCommit: boolean = false) =>
213
+ state.runUpdate((get, set) =>
214
+ Effect.gen(function*(_) {
215
+ const state = yield* _(get)
216
+ const from = state.entries[state.index]
217
+ const history = options?.history ?? "auto"
218
+ const to = yield* _(
219
+ makeOrUpdateDestination(state, getUrl(origin, pathOrUrl), options?.state, origin),
220
+ GetRandomValues.provide(getRandomValues)
221
+ )
222
+ const type = history === "auto" ? from.key === to.key ? "replace" : "push" : history
223
+ const event: BeforeNavigationEvent = {
224
+ type,
225
+ from,
226
+ to,
227
+ delta: type === "replace" ? 0 : 1,
228
+ info: options?.info
229
+ }
230
+
231
+ return yield* _(runNavigationEvent(event, get, set, 0, skipCommit))
232
+ })
233
+ )
234
+
235
+ const traverseTo = (key: Destination["key"], options?: { readonly info?: unknown }, skipCommit: boolean = false) =>
236
+ state.runUpdate((get, set) =>
237
+ Effect.gen(function*(_) {
238
+ const state = yield* _(get)
239
+ const { entries, index } = state
240
+ const from = entries[index]
241
+ const nextIndex = entries.findIndex((e) => e.key === key)
242
+
243
+ if (nextIndex === -1) return from
244
+
245
+ const id = yield* _(makeUuid, GetRandomValues.provide(getRandomValues))
246
+ const to = { ...entries[nextIndex], id }
247
+ const delta = nextIndex - index
248
+ const event: BeforeNavigationEvent = {
249
+ type: "traverse",
250
+ from,
251
+ to,
252
+ delta,
253
+ info: options?.info
254
+ }
255
+
256
+ return yield* _(runNavigationEvent(event, get, set, 0, skipCommit))
257
+ })
258
+ )
259
+
260
+ const back = (options?: { readonly info?: unknown }, skipCommit: boolean = false) =>
261
+ Effect.gen(function*(_) {
262
+ const { entries, index } = yield* _(state)
263
+ if (index === 0) return entries[index]
264
+ const { key } = entries[index - 1]
265
+
266
+ return yield* _(traverseTo(key, options, skipCommit))
267
+ })
268
+
269
+ const forward = (options?: { readonly info?: unknown }, skipCommit: boolean = false) =>
270
+ Effect.gen(function*(_) {
271
+ const { entries, index } = yield* _(state)
272
+ if (index === entries.length - 1) return entries[index]
273
+ const { key } = entries[index + 1]
274
+
275
+ return yield* _(traverseTo(key, options, skipCommit))
276
+ })
277
+
278
+ const reload = (options?: { readonly info?: unknown }, skipCommit: boolean = false) =>
279
+ state.runUpdate((get, set) =>
280
+ Effect.gen(function*(_) {
281
+ const { entries, index } = yield* _(state)
282
+ const current = entries[index]
283
+
284
+ const event: BeforeNavigationEvent = {
285
+ type: "reload",
286
+ from: current,
287
+ to: current,
288
+ delta: 0,
289
+ info: options?.info
290
+ }
291
+
292
+ return yield* _(runNavigationEvent(event, get, set, 0, skipCommit))
293
+ })
294
+ )
295
+
296
+ const beforeNavigation = <R = never, R2 = never>(
297
+ handler: BeforeNavigationHandler<R, R2>
298
+ ): Effect.Effect<R | R2 | Scope.Scope, never, void> =>
299
+ Effect.contextWithEffect((ctx) => {
300
+ const entry = [handler, ctx] as const
301
+
302
+ return Effect.zipRight(
303
+ beforeHandlers.update((handlers) => new Set([...handlers, entry])),
304
+ Effect.addFinalizer(() =>
305
+ beforeHandlers.update((handlers) => {
306
+ const updated = new Set(handlers)
307
+ updated.delete(entry)
308
+ return updated
309
+ })
310
+ )
311
+ )
312
+ })
313
+
314
+ const onNavigation = <R = never, R2 = never>(
315
+ handler: NavigationHandler<R, R2>
316
+ ): Effect.Effect<R | R2 | Scope.Scope, never, void> =>
317
+ Effect.contextWithEffect((ctx) => {
318
+ const entry = [handler, ctx] as const
319
+
320
+ return Effect.zipRight(
321
+ handlers.update((handlers) => new Set([...handlers, entry])),
322
+ Effect.addFinalizer(() =>
323
+ handlers.update((handlers) => {
324
+ const updated = new Set(handlers)
325
+ updated.delete(entry)
326
+ return updated
327
+ })
328
+ )
329
+ )
330
+ })
331
+
332
+ const updateCurrentEntry = (options: { readonly state: unknown }) =>
333
+ state.runUpdate((get, set) =>
334
+ Effect.gen(function*(_) {
335
+ const { entries, index } = yield* _(get)
336
+ const current = entries[index]
337
+ const event: BeforeNavigationEvent = {
338
+ type: "replace",
339
+ from: current,
340
+ to: { ...current, state: options.state },
341
+ delta: 0,
342
+ info: null
343
+ }
344
+
345
+ return yield* _(runNavigationEvent(event, get, set, 0))
346
+ })
347
+ )
348
+
349
+ const navigation = {
350
+ back,
351
+ base,
352
+ beforeNavigation,
353
+ canGoBack,
354
+ canGoForward,
355
+ currentEntry,
356
+ entries,
357
+ forward,
358
+ navigate,
359
+ onNavigation,
360
+ origin,
361
+ reload,
362
+ transition,
363
+ traverseTo,
364
+ updateCurrentEntry
365
+ } satisfies Navigation
366
+
367
+ return navigation
368
+ }
369
+
370
+ export function makeRedirectEvent(
371
+ origin: string,
372
+ redirect: RedirectError,
373
+ from: Destination
374
+ ) {
375
+ return Effect.gen(function*(_) {
376
+ const url = getUrl(origin, redirect.path)
377
+ const to = yield* _(makeDestination(url, redirect.options?.state, origin))
378
+ const event: BeforeNavigationEvent = {
379
+ type: "replace",
380
+ from,
381
+ to,
382
+ delta: 0,
383
+ info: redirect.options?.info
384
+ }
385
+
386
+ return event
387
+ })
388
+ }
389
+
390
+ export function makeOrUpdateDestination(
391
+ navigationState: NavigationState,
392
+ url: URL,
393
+ state: unknown,
394
+ origin: string
395
+ ) {
396
+ return Effect.gen(function*(_) {
397
+ const current = navigationState.entries[navigationState.index]
398
+ const isSameOriginAndPath = url.origin === current.url.origin && url.pathname === current.url.pathname
399
+
400
+ if (isSameOriginAndPath) {
401
+ const id = yield* _(makeUuid)
402
+ const destination: Destination = {
403
+ id,
404
+ key: current.key,
405
+ url,
406
+ state: getOriginalState(state),
407
+ sameDocument: url.origin === origin
408
+ }
409
+
410
+ return destination
411
+ } else {
412
+ return yield* _(makeDestination(url, state, origin))
413
+ }
414
+ })
415
+ }
416
+
417
+ export function makeDestination(url: URL, state: unknown, origin: string) {
418
+ return Effect.gen(function*(_) {
419
+ if (isPatchedState(state)) {
420
+ const destination: Destination = {
421
+ id: state.id,
422
+ key: state.key,
423
+ url,
424
+ state: state.originalHistoryState,
425
+ sameDocument: url.origin === origin
426
+ }
427
+
428
+ return destination
429
+ }
430
+
431
+ const id = yield* _(makeUuid)
432
+ const key = yield* _(makeUuid)
433
+
434
+ const destination: Destination = {
435
+ id,
436
+ key,
437
+ url,
438
+ state,
439
+ sameDocument: url.origin === origin
440
+ }
441
+
442
+ return destination
443
+ })
444
+ }
445
+
446
+ export function upgradeProposedDestination(proposed: ProposedDestination) {
447
+ return Effect.gen(function*(_) {
448
+ const id = yield* _(makeUuid)
449
+ const key = yield* _(makeUuid)
450
+
451
+ const destination: Destination = {
452
+ id,
453
+ key,
454
+ url: proposed.url,
455
+ state: proposed.state,
456
+ sameDocument: proposed.sameDocument
457
+ }
458
+
459
+ return destination
460
+ })
461
+ }
462
+
463
+ export type PatchedState = {
464
+ readonly id: Uuid
465
+ readonly key: Uuid
466
+ readonly originalHistoryState: unknown
467
+ }
468
+
469
+ export function isPatchedState(state: unknown): state is PatchedState {
470
+ if (state === null || !(typeof state === "object") || Array.isArray(state)) return false
471
+ if ("id" in state && "key" in state && "originalHistoryState" in state) return true
472
+ return false
473
+ }
474
+
475
+ export function getOriginalState(state: unknown) {
476
+ if (isPatchedState(state)) return state.originalHistoryState
477
+ return state
478
+ }
479
+
480
+ export function getOriginFromUrl(url: string | URL) {
481
+ try {
482
+ if (typeof url === "string") {
483
+ return new URL(url).origin
484
+ } else {
485
+ return url.origin
486
+ }
487
+ } catch {
488
+ return "http://localhost"
489
+ }
490
+ }
491
+
492
+ export function isDestination(proposed: ProposedDestination): proposed is Destination {
493
+ return "id" in proposed && "key" in proposed
494
+ }
495
+
496
+ export function makeHandlersState() {
497
+ return Effect.gen(function*(_) {
498
+ const beforeHandlers = yield* _(
499
+ RefSubject.fromEffect(
500
+ Effect.sync(() => new Set<readonly [BeforeNavigationHandler<any, any>, Context.Context<any>]>())
501
+ )
502
+ )
503
+ const handlers = yield* _(
504
+ RefSubject.fromEffect(
505
+ Effect.sync(() => new Set<readonly [NavigationHandler<any, any>, Context.Context<any>]>())
506
+ )
507
+ )
508
+
509
+ return {
510
+ beforeHandlers,
511
+ handlers
512
+ }
513
+ })
514
+ }
package/dist/DOM.d.ts DELETED
@@ -1,12 +0,0 @@
1
- import * as Layer from '@effect/io/Layer';
2
- import { Location, History, Window, Storage, Document } from '@typed/dom';
3
- import { DestinationKey, Navigation } from './Navigation.js';
4
- export type NavigationServices = Window | Document | Location | History | Storage;
5
- export interface DomNavigationOptions {
6
- readonly initialKey?: DestinationKey;
7
- readonly maxEntries?: number;
8
- }
9
- export declare const dom: (options?: DomNavigationOptions) => Layer.Layer<NavigationServices, never, Navigation>;
10
- export declare function getBasePathFromHref(href: string): string;
11
- export declare function getCurrentPathFromLocation(location: Location | HTMLAnchorElement | URL): string;
12
- //# sourceMappingURL=DOM.d.ts.map
package/dist/DOM.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"DOM.d.ts","sourceRoot":"","sources":["../src/DOM.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,kBAAkB,CAAA;AAEzC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAqB,QAAQ,EAAE,MAAM,YAAY,CAAA;AAG5F,OAAO,EAAe,cAAc,EAAE,UAAU,EAAmB,MAAM,iBAAiB,CAAA;AAM1F,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAA;AAEjF,MAAM,WAAW,oBAAoB;IAGnC,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,CAAA;IAIpC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED,eAAO,MAAM,GAAG,aACL,oBAAoB,KAC5B,WAAW,CAAC,kBAAkB,EAAE,KAAK,EAAE,UAAU,CAuHnD,CAAA;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,UAQ/C;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,QAAQ,GAAG,iBAAiB,GAAG,GAAG,UAEtF"}
package/dist/DOM.js DELETED
@@ -1,87 +0,0 @@
1
- import { pipe } from '@effect/data/Function';
2
- import * as Option from '@effect/data/Option';
3
- import * as Effect from '@effect/io/Effect';
4
- import * as Context from '@typed/context';
5
- import { addWindowListener, Document } from '@typed/dom';
6
- import * as Fx from '@typed/fx';
7
- import { Navigation } from './Navigation.js';
8
- import { makeIntent } from './dom-intent.js';
9
- import { onHistoryEvent, patchHistory } from './history.js';
10
- import { makeModel } from './model.js';
11
- import { getInitialValues } from './storage.js';
12
- export const dom = (options = {}) => {
13
- return Navigation.layerScoped(Effect.gen(function* ($) {
14
- // Get resources
15
- const context = yield* $(Effect.context());
16
- const document = Context.get(context, Document);
17
- const base = document.querySelector('base');
18
- const baseHref = base ? getBasePathFromHref(base.href) : '/';
19
- // Patch History API to enable sending events
20
- const [history, historyEvents] = yield* $(patchHistory);
21
- // Create model and intent
22
- const [initialEntries, initialIndex] = yield* $(getInitialValues(baseHref, options));
23
- const model = yield* $(makeModel(initialEntries, initialIndex));
24
- const intent = makeIntent(model, baseHref, history, options);
25
- // Used to ensure ordering of navigation events
26
- const lock = Effect.unsafeMakeSemaphore(1).withPermits(1);
27
- const handleNavigationError = (depth) => (error) => Effect.provideContext(Effect.gen(function* ($) {
28
- if (depth >= 50) {
29
- throw new Error('Too many redirects. You may have an infinite loop of onNavigation handlers that are redirecting.');
30
- }
31
- switch (error._tag) {
32
- case 'NoSuchElementException':
33
- case 'CancelNavigation':
34
- return yield* $(model.currentEntry.get);
35
- case 'RedirectNavigation':
36
- return yield* $(Effect.catchAll(intent.navigate(error.url, error), handleNavigationError(depth + 1)));
37
- }
38
- }), context);
39
- const catchNavigationError = (effect) => Effect.catchAll(effect, handleNavigationError(0));
40
- // Used to provide a locked effect with the current context
41
- const provideLocked = (effect) => Effect.provideContext(lock(effect), context);
42
- // Constructor our service
43
- const navigation = {
44
- back: provideLocked(catchNavigationError(intent.back(false))),
45
- base: baseHref,
46
- canGoBack: model.canGoBack,
47
- canGoForward: model.canGoForward,
48
- currentEntry: model.currentEntry,
49
- entries: model.entries,
50
- forward: provideLocked(catchNavigationError(intent.forward(false))),
51
- goTo: (a) => pipe(a, intent.goTo, Effect.catchAll((a) => pipe(a, handleNavigationError(0), Effect.map(Option.some))), provideLocked),
52
- navigate: (url, options) => pipe(intent.navigate(url, options), catchNavigationError, provideLocked),
53
- onNavigation: (handler, options) => pipe(intent.onNavigation(handler, options), catchNavigationError, Effect.asUnit),
54
- onNavigationEnd: (handler, options) => Effect.asUnit(intent.onNavigationEnd(handler, options)),
55
- reload: provideLocked(catchNavigationError(intent.reload)),
56
- };
57
- // Listen to various events and update our model
58
- yield* $(Fx.mergeAll(
59
- // Listen to history events and keep track of entries
60
- pipe(historyEvents, Fx.mapEffect((event) => lock(onHistoryEvent(event, intent)))),
61
- // Listen to hash changes and push them to the history
62
- pipe(addWindowListener('hashchange', { capture: true }), Fx.mapEffect((ev) => lock(intent.push(ev.newURL, { state: history.state }, true)))),
63
- // Listen to popstate events and go to the correct entry
64
- pipe(addWindowListener('popstate'), Fx.mapEffect(Effect.unifiedFn((ev) => {
65
- // TODO: Should we throw some kind of error here?
66
- // This should never happen if you are solely using the Navigation Service
67
- if (!ev.state || !ev.state.key) {
68
- return lock(intent.push(location.href, { state: history.state }, true));
69
- }
70
- return lock(intent.goTo(ev.state.key));
71
- })))), Fx.drain, Effect.forkScoped);
72
- return navigation;
73
- }));
74
- };
75
- export function getBasePathFromHref(href) {
76
- try {
77
- const url = new URL(href);
78
- return getCurrentPathFromLocation(url);
79
- }
80
- catch {
81
- return href;
82
- }
83
- }
84
- export function getCurrentPathFromLocation(location) {
85
- return location.pathname + location.search + location.hash;
86
- }
87
- //# sourceMappingURL=DOM.js.map
package/dist/DOM.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"DOM.js","sourceRoot":"","sources":["../src/DOM.ts"],"names":[],"mappings":"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"}
package/dist/Memory.d.ts DELETED
@@ -1,10 +0,0 @@
1
- import * as Layer from '@effect/io/Layer';
2
- import type { DomNavigationOptions } from './DOM.js';
3
- import { Navigation } from './Navigation.js';
4
- export interface MemoryNavigationOptions extends DomNavigationOptions {
5
- readonly initialUrl: URL;
6
- readonly initialState?: unknown;
7
- readonly base?: string;
8
- }
9
- export declare function memory(options: MemoryNavigationOptions): Layer.Layer<never, never, Navigation>;
10
- //# sourceMappingURL=Memory.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Memory.d.ts","sourceRoot":"","sources":["../src/Memory.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,kBAAkB,CAAA;AAEzC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AACpD,OAAO,EAEL,UAAU,EAIX,MAAM,iBAAiB,CAAA;AAKxB,MAAM,WAAW,uBAAwB,SAAQ,oBAAoB;IACnE,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAA;IACxB,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wBAAgB,MAAM,CAAC,OAAO,EAAE,uBAAuB,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,CA6E9F"}