@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,26 +1,29 @@
1
- import type { HttpClientError, HttpClientResponse } from "@effect/platform";
2
- import { HttpClient } from "@effect/platform";
3
- import { Schema } from "@effect/schema";
4
- import type * as Context from "@typed/context";
5
- import * as RefSubject from "@typed/fx/RefSubject";
6
- import type { Uuid } from "@typed/id";
7
- import { GetRandomValues } from "@typed/id";
8
- import * as Effect from "effect/Effect";
9
- import * as Option from "effect/Option";
10
- import type * as Scope from "effect/Scope";
11
- import type { Commit } from "../Layer.js";
12
- import type { BeforeNavigationEvent, BeforeNavigationHandler, FormDataHandler, FormInputFrom, NavigateOptions, NavigationError, NavigationHandler, ProposedDestination, RedirectError } from "../Navigation.js";
13
- import { Destination, Transition } from "../Navigation.js";
1
+ import * as HttpClient from '@effect/platform/HttpClient';
2
+ import { GetRandomValues, type Uuid4 } from '@typed/id';
3
+ import * as LazyRef from '@typed/lazy-ref';
4
+ import { Schema } from 'effect';
5
+ import type * as Context from 'effect/Context';
6
+ import * as Effect from 'effect/Effect';
7
+ import * as Option from 'effect/Option';
8
+ import type * as Scope from 'effect/Scope';
9
+ import { Destination } from '../Destination.js';
10
+ import { FormSubmitError, type NavigationError } from '../Error.js';
11
+ import { TransitionEvent } from '../Event.js';
12
+ import type { FormSubmit } from '../Forms.js';
13
+ import type { BeforeNavigationHandler, NavigationHandler } from '../Handler.js';
14
+ import type { Commit } from '../Layer.js';
15
+ import type { NavigateOptions } from '../NavigateOptions.js';
16
+ import type { ProposedDestination } from '../ProposedDestination.js';
14
17
  export type NavigationState = {
15
18
  readonly entries: ReadonlyArray<Destination>;
16
19
  readonly index: number;
17
- readonly transition: Option.Option<Transition>;
20
+ readonly transition: Option.Option<TransitionEvent>;
18
21
  };
19
22
  export declare const NavigationState: Schema.Struct<{
20
23
  entries: Schema.Array$<Schema.Struct<{
21
- id: Schema.Schema<Uuid, string, never>;
22
- key: Schema.Schema<Uuid, string, never>;
23
- url: Schema.transformOrFail<typeof Schema.String, Schema.Schema<URL, URL, never>, never>;
24
+ id: typeof Schema.UUID;
25
+ key: typeof Schema.UUID;
26
+ url: Schema.transformOrFail<typeof Schema.String, Schema.instanceOf<URL>, never>;
24
27
  state: typeof Schema.Unknown;
25
28
  sameDocument: typeof Schema.Boolean;
26
29
  }>>;
@@ -28,12 +31,13 @@ export declare const NavigationState: Schema.Struct<{
28
31
  transition: Schema.OptionFromNullishOr<Schema.Struct<{
29
32
  type: Schema.Literal<["push", "replace", "reload", "traverse"]>;
30
33
  from: Schema.Struct<{
31
- id: Schema.Schema<Uuid, string, never>;
32
- key: Schema.Schema<Uuid, string, never>;
33
- url: Schema.transformOrFail<typeof Schema.String, Schema.Schema<URL, URL, never>, never>;
34
+ id: typeof Schema.UUID;
35
+ key: typeof Schema.UUID;
36
+ url: Schema.transformOrFail<typeof Schema.String, Schema.instanceOf<URL>, never>;
34
37
  state: typeof Schema.Unknown;
35
38
  sameDocument: typeof Schema.Boolean;
36
39
  }>;
40
+ delta: typeof Schema.Number;
37
41
  to: Schema.Union<[Schema.SchemaClass<{
38
42
  readonly url: URL;
39
43
  readonly state: unknown;
@@ -43,34 +47,32 @@ export declare const NavigationState: Schema.Struct<{
43
47
  readonly state: unknown;
44
48
  readonly sameDocument: boolean;
45
49
  }, never>, Schema.Struct<{
46
- id: Schema.Schema<Uuid, string, never>;
47
- key: Schema.Schema<Uuid, string, never>;
48
- url: Schema.transformOrFail<typeof Schema.String, Schema.Schema<URL, URL, never>, never>;
50
+ id: typeof Schema.UUID;
51
+ key: typeof Schema.UUID;
52
+ url: Schema.transformOrFail<typeof Schema.String, Schema.instanceOf<URL>, never>;
49
53
  state: typeof Schema.Unknown;
50
54
  sameDocument: typeof Schema.Boolean;
51
55
  }>]>;
56
+ info: typeof Schema.Unknown;
52
57
  }>>;
53
58
  }>;
54
- export declare const getUrl: (origin: string, urlOrPath: string | URL) => URL;
59
+ export declare function getUrl(origin: string, urlOrPath: string | URL, base: string): URL;
55
60
  export type ModelAndIntent = {
56
- readonly state: RefSubject.RefSubject<NavigationState>;
57
- readonly canGoBack: RefSubject.Computed<boolean>;
58
- readonly canGoForward: RefSubject.Computed<boolean>;
59
- readonly beforeHandlers: RefSubject.RefSubject<Set<readonly [BeforeNavigationHandler<any, any>, Context.Context<any>]>>;
60
- readonly handlers: RefSubject.RefSubject<Set<readonly [NavigationHandler<any, any>, Context.Context<any>]>>;
61
- readonly formDataHandlers: RefSubject.RefSubject<Set<readonly [FormDataHandler<any, any>, Context.Context<any>]>>;
61
+ readonly state: LazyRef.LazyRef<NavigationState>;
62
+ readonly beforeHandlers: LazyRef.LazyRef<Set<readonly [BeforeNavigationHandler<any, any>, Context.Context<any>]>>;
63
+ readonly handlers: LazyRef.LazyRef<Set<readonly [NavigationHandler<any, any>, Context.Context<any>]>>;
62
64
  readonly commit: Commit;
63
65
  };
64
- export declare function setupFromModelAndIntent(modelAndIntent: ModelAndIntent, origin: string, base: string, getRandomValues: Context.Fn.FnOf<typeof GetRandomValues>, newNavigationState?: () => NavigationState): {
66
+ export declare function setupFromModelAndIntent(modelAndIntent: ModelAndIntent, origin: string, base: string, getRandomValues: Context.Tag.Service<typeof GetRandomValues>, newNavigationState?: () => NavigationState): {
65
67
  back: (options?: {
66
68
  readonly info?: unknown;
67
69
  }, skipCommit?: boolean) => Effect.Effect<Destination, NavigationError, never>;
68
70
  base: string;
69
71
  beforeNavigation: <R = never, R2 = never>(handler: BeforeNavigationHandler<R, R2>) => Effect.Effect<void, never, R | R2 | Scope.Scope>;
70
- canGoBack: RefSubject.Computed<boolean, never, never>;
71
- canGoForward: RefSubject.Computed<boolean, never, never>;
72
- currentEntry: RefSubject.Computed<Destination, never, never>;
73
- entries: RefSubject.Computed<readonly Destination[], never, never>;
72
+ canGoBack: LazyRef.Computed<boolean, never, never>;
73
+ canGoForward: LazyRef.Computed<boolean, never, never>;
74
+ currentEntry: LazyRef.Computed<Destination, never, never>;
75
+ entries: LazyRef.Computed<readonly Destination[], never, never>;
74
76
  forward: (options?: {
75
77
  readonly info?: unknown;
76
78
  }, skipCommit?: boolean) => Effect.Effect<Destination, NavigationError, never>;
@@ -80,32 +82,28 @@ export declare function setupFromModelAndIntent(modelAndIntent: ModelAndIntent,
80
82
  reload: (options?: {
81
83
  readonly info?: unknown;
82
84
  }, skipCommit?: boolean) => Effect.Effect<Destination, NavigationError, never>;
83
- transition: RefSubject.Computed<Option.Option<Transition>, never, never>;
85
+ transition: LazyRef.Computed<Option.Option<TransitionEvent>, never, never>;
84
86
  traverseTo: (key: Destination["key"], options?: {
85
87
  readonly info?: unknown;
86
88
  }, skipCommit?: boolean) => Effect.Effect<Destination, NavigationError, never>;
87
89
  updateCurrentEntry: (options: {
88
90
  readonly state: unknown;
89
91
  }) => Effect.Effect<Destination, NavigationError, never>;
90
- submit: (data: FormData, input?: Omit<FormInputFrom, "data">) => Effect.Effect<Option.Option<HttpClientResponse.HttpClientResponse>, NavigationError | HttpClientError.HttpClientError, Scope.Scope | HttpClient.HttpClient.Service>;
91
- onFormData: <R = never, R2 = never>(handler: FormDataHandler<R, R2>) => Effect.Effect<void, never, R | R2 | Scope.Scope>;
92
+ submit: (form: FormSubmit) => Effect.Effect<readonly [Destination, import("@effect/platform/HttpClientResponse").HttpClientResponse], NavigationError | FormSubmitError, Scope.Scope | HttpClient.HttpClient>;
92
93
  };
93
- export declare function makeRedirectEvent(origin: string, redirect: RedirectError, from: Destination): Effect.Effect<BeforeNavigationEvent, never, GetRandomValues>;
94
94
  export declare function makeOrUpdateDestination(navigationState: NavigationState, url: URL, state: unknown, origin: string): Effect.Effect<Destination, never, GetRandomValues>;
95
95
  export declare function makeDestination(url: URL, state: unknown, origin: string): Effect.Effect<Destination, never, GetRandomValues>;
96
96
  export declare function upgradeProposedDestination(proposed: ProposedDestination): Effect.Effect<Destination, never, GetRandomValues>;
97
97
  export type PatchedState = {
98
- readonly __typed__navigation__id__: Uuid;
99
- readonly __typed__navigation__key__: Uuid;
98
+ readonly __typed__navigation__id__: Uuid4;
99
+ readonly __typed__navigation__key__: Uuid4;
100
100
  readonly __typed__navigation__state__: unknown;
101
101
  };
102
102
  export declare function isPatchedState(state: unknown): state is PatchedState;
103
103
  export declare function getOriginalState(state: unknown): unknown;
104
104
  export declare function getOriginFromUrl(url: string | URL): string;
105
105
  export declare function isDestination(proposed: ProposedDestination): proposed is Destination;
106
- export declare function makeHandlersState(): Effect.Effect<{
107
- beforeHandlers: RefSubject.RefSubject<Set<readonly [BeforeNavigationHandler<any, any>, Context.Context<any>]>, never, never>;
108
- handlers: RefSubject.RefSubject<Set<readonly [NavigationHandler<any, any>, Context.Context<any>]>, never, never>;
109
- formDataHandlers: RefSubject.RefSubject<Set<readonly [FormDataHandler<any, any>, Context.Context<any>]>, never, never>;
106
+ export declare const makeHandlersState: Effect.Effect<{
107
+ beforeHandlers: LazyRef.LazyRef<Set<readonly [BeforeNavigationHandler<any, any>, Context.Context<any>]>, never, never>;
108
+ handlers: LazyRef.LazyRef<Set<readonly [NavigationHandler<any, any>, Context.Context<any>]>, never, never>;
110
109
  }, never, Scope.Scope>;
111
- //# sourceMappingURL=shared.d.ts.map
@@ -1,25 +1,54 @@
1
- import { Headers, HttpClient, HttpClientRequest } from "@effect/platform";
2
- import { Schema } from "@effect/schema";
3
- import * as RefSubject from "@typed/fx/RefSubject";
4
- import { GetRandomValues, makeUuid } from "@typed/id";
5
- import * as Effect from "effect/Effect";
6
- import * as Either from "effect/Either";
7
- import * as Option from "effect/Option";
8
- import { Destination, Transition } from "../Navigation.js";
1
+ import * as Headers from '@effect/platform/Headers';
2
+ import * as HttpClient from '@effect/platform/HttpClient';
3
+ import { GetRandomValues, makeUuid4 } from '@typed/id';
4
+ import * as LazyRef from '@typed/lazy-ref';
5
+ import { Cause, Schema } 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 { Destination } from '../Destination.js';
10
+ import { FormSubmitError, } from '../Error.js';
11
+ import { TransitionEvent } from '../Event.js';
9
12
  export const NavigationState = Schema.Struct({
10
13
  entries: Schema.Array(Destination),
11
14
  index: Schema.Number,
12
- transition: Schema.OptionFromNullishOr(Transition, null)
15
+ transition: Schema.OptionFromNullishOr(TransitionEvent, null),
13
16
  });
14
- export const getUrl = (origin, urlOrPath) => {
15
- return typeof urlOrPath === "string" ? new URL(urlOrPath, origin) : urlOrPath;
16
- };
17
+ export function getUrl(origin, urlOrPath, base) {
18
+ if (typeof urlOrPath === 'string') {
19
+ const url = new URL(urlOrPath, origin);
20
+ // If the URL is relative, join it with the base
21
+ if (isRelativeUrl(urlOrPath)) {
22
+ url.pathname = joinPath(base, url.pathname);
23
+ }
24
+ return url;
25
+ }
26
+ return urlOrPath;
27
+ }
28
+ function isRelativeUrl(url) {
29
+ if (url.includes('://'))
30
+ return false;
31
+ return true;
32
+ }
33
+ function joinPath(a, b) {
34
+ const aEndsWithSlash = a[a.length - 1] === '/';
35
+ const bStartsWithSlash = b[0] === '/';
36
+ if (aEndsWithSlash && bStartsWithSlash) {
37
+ return a + b.slice(1);
38
+ }
39
+ if (!aEndsWithSlash && !bStartsWithSlash) {
40
+ return `${a}/${b}`;
41
+ }
42
+ return a + b;
43
+ }
17
44
  const REDIRECT_STATUS_CODES = new Set([301, 302, 303, 307, 308]);
18
45
  export function setupFromModelAndIntent(modelAndIntent, origin, base, getRandomValues, newNavigationState) {
19
- const { beforeHandlers, canGoBack, canGoForward, commit, formDataHandlers, handlers, state } = modelAndIntent;
20
- const entries = RefSubject.map(state, (s) => s.entries);
21
- const currentEntry = RefSubject.map(state, (s) => s.entries[s.index]);
22
- const transition = RefSubject.map(state, (s) => s.transition);
46
+ const { beforeHandlers, commit, handlers, state } = modelAndIntent;
47
+ const canGoBack = LazyRef.map(state, (s) => s.index > 0);
48
+ const canGoForward = LazyRef.map(state, (s) => s.index < s.entries.length - 1);
49
+ const entries = LazyRef.map(state, (s) => s.entries);
50
+ const currentEntry = LazyRef.map(state, (s) => s.entries[s.index]);
51
+ const transition = LazyRef.map(state, (s) => s.transition);
23
52
  const runBeforeHandlers = (event) => Effect.gen(function* () {
24
53
  const handlers = yield* beforeHandlers;
25
54
  const matches = [];
@@ -27,7 +56,7 @@ export function setupFromModelAndIntent(modelAndIntent, origin, base, getRandomV
27
56
  const exit = yield* handler(event).pipe(Effect.provide(ctx), Effect.either);
28
57
  if (Either.isRight(exit)) {
29
58
  const match = exit.right;
30
- if (Option.isSome(match)) {
59
+ if (match !== undefined && Option.isSome(match)) {
31
60
  matches.push(Effect.provide(match.value, ctx));
32
61
  }
33
62
  }
@@ -50,7 +79,7 @@ export function setupFromModelAndIntent(modelAndIntent, origin, base, getRandomV
50
79
  const matches = [];
51
80
  for (const [handler, ctx] of eventHandlers) {
52
81
  const match = yield* Effect.provide(handler(event), ctx);
53
- if (Option.isSome(match)) {
82
+ if (match !== undefined && Option.isSome(match)) {
54
83
  matches.push(Effect.provide(match.value, ctx));
55
84
  }
56
85
  }
@@ -58,44 +87,10 @@ export function setupFromModelAndIntent(modelAndIntent, origin, base, getRandomV
58
87
  yield* Effect.all(matches, { discard: true });
59
88
  }
60
89
  });
61
- const runFormDataHandlers = (event) => Effect.gen(function* () {
62
- const handlers = yield* formDataHandlers;
63
- const matches = [];
64
- for (const [handler, ctx] of handlers) {
65
- const exit = yield* handler(event).pipe(Effect.provide(ctx), Effect.either);
66
- if (Either.isRight(exit)) {
67
- const match = exit.right;
68
- if (Option.isSome(match)) {
69
- matches.push(Effect.provide(match.value, ctx));
70
- }
71
- }
72
- else {
73
- return Either.left(exit.left);
74
- }
75
- }
76
- if (matches.length > 0) {
77
- for (const match of matches) {
78
- const exit = yield* Effect.either(match);
79
- if (Either.isLeft(exit)) {
80
- return Either.left(exit.left);
81
- }
82
- else if (Option.isSome(exit.right)) {
83
- return Either.right(exit.right);
84
- }
85
- }
86
- }
87
- else {
88
- // Only if there are 0 matches, we'll make a request to the server ourselves
89
- const response = yield* makeFormDataRequest(event, Option.getOrElse(event.action, () => event.from.url.href));
90
- return Either.right(Option.some(response));
91
- }
92
- return Either.right(Option.none());
93
- });
94
90
  const runNavigationEvent = (beforeEvent, get, set, depth, skipCommit = false) => Effect.gen(function* () {
95
- let current = yield* get;
96
- current = yield* set({
97
- ...current,
98
- transition: Option.some(beforeEvent)
91
+ const current = yield* set({
92
+ ...(yield* get),
93
+ transition: Option.some(beforeEvent),
99
94
  });
100
95
  if (!skipCommit) {
101
96
  const beforeError = yield* runBeforeHandlers(beforeEvent);
@@ -109,7 +104,7 @@ export function setupFromModelAndIntent(modelAndIntent, origin, base, getRandomV
109
104
  if (!skipCommit) {
110
105
  yield* commit(to, beforeEvent);
111
106
  }
112
- if (newNavigationState) {
107
+ if (newNavigationState !== undefined) {
113
108
  const { entries, index } = yield* set(newNavigationState());
114
109
  return entries[index];
115
110
  }
@@ -117,21 +112,21 @@ export function setupFromModelAndIntent(modelAndIntent, origin, base, getRandomV
117
112
  const event = {
118
113
  type: beforeEvent.type,
119
114
  info: beforeEvent.info,
120
- destination: to
115
+ destination: to,
121
116
  };
122
- if (beforeEvent.type === "push") {
117
+ if (beforeEvent.type === 'push') {
123
118
  const index = current.index + 1;
124
119
  const entries = current.entries.slice(0, index).concat([to]);
125
120
  yield* set({ entries, index, transition: Option.none() });
126
121
  }
127
- else if (beforeEvent.type === "replace") {
122
+ else if (beforeEvent.type === 'replace') {
128
123
  const index = current.index;
129
124
  const before = current.entries.slice(0, index);
130
125
  const after = current.entries.slice(index + 1);
131
126
  const entries = [...before, to, ...after];
132
127
  yield* set({ entries, index, transition: Option.none() });
133
128
  }
134
- else if (beforeEvent.type === "reload") {
129
+ else if (beforeEvent.type === 'reload') {
135
130
  yield* set({ ...current, transition: Option.none() });
136
131
  }
137
132
  else {
@@ -140,44 +135,40 @@ export function setupFromModelAndIntent(modelAndIntent, origin, base, getRandomV
140
135
  yield* set({
141
136
  ...current,
142
137
  index: nextIndex,
143
- transition: Option.none()
138
+ transition: Option.none(),
144
139
  });
145
140
  }
146
141
  yield* runHandlers(event);
147
142
  }
148
143
  return to;
149
- }).pipe(GetRandomValues.provide(getRandomValues));
144
+ }).pipe(Effect.provideService(GetRandomValues, getRandomValues));
150
145
  const handleError = (error, get, set, depth) => Effect.gen(function* () {
151
146
  if (depth >= 25) {
152
- return yield* Effect.dieMessage(`Redirect loop detected.`);
147
+ return yield* Effect.dieMessage('Redirect loop detected.');
153
148
  }
154
149
  const { entries, index } = yield* get;
155
150
  const from = entries[index];
156
- if (error._tag === "CancelNavigation") {
151
+ if (error._tag === 'CancelNavigation') {
157
152
  yield* set({ entries, index, transition: Option.none() });
158
153
  return from;
159
154
  }
160
155
  else {
161
- const event = yield* makeRedirectEvent(origin, error, from);
156
+ const event = yield* makeRedirectEvent(origin, error, from, base);
162
157
  return yield* runNavigationEvent(event, get, set, depth + 1);
163
158
  }
164
- }).pipe(GetRandomValues.provide(getRandomValues));
159
+ }).pipe(Effect.provideService(GetRandomValues, getRandomValues));
165
160
  const navigate = (pathOrUrl, options, skipCommit = false) => state.runUpdates(({ get, set }) => Effect.gen(function* () {
166
161
  const state = yield* get;
167
162
  const from = state.entries[state.index];
168
- const history = options?.history ?? "auto";
169
- const to = yield* makeOrUpdateDestination(state, getUrl(origin, pathOrUrl), options?.state, origin).pipe(GetRandomValues.provide(getRandomValues));
170
- const type = history === "auto"
171
- ? from.key === to.key
172
- ? "replace"
173
- : "push"
174
- : history;
163
+ const history = options?.history ?? 'auto';
164
+ const to = yield* makeOrUpdateDestination(state, getUrl(origin, pathOrUrl, base), options?.state, origin).pipe(Effect.provideService(GetRandomValues, getRandomValues));
165
+ const type = history === 'auto' ? (from.key === to.key ? 'replace' : 'push') : history;
175
166
  const event = {
176
167
  type,
177
168
  from,
178
169
  to,
179
- delta: type === "replace" ? 0 : 1,
180
- info: options?.info
170
+ delta: type === 'replace' ? 0 : 1,
171
+ info: options?.info,
181
172
  };
182
173
  return yield* runNavigationEvent(event, get, set, 0, skipCommit);
183
174
  }));
@@ -188,15 +179,15 @@ export function setupFromModelAndIntent(modelAndIntent, origin, base, getRandomV
188
179
  const nextIndex = entries.findIndex((e) => e.key === key);
189
180
  if (nextIndex === -1)
190
181
  return from;
191
- const id = yield* makeUuid.pipe(GetRandomValues.provide(getRandomValues));
182
+ const id = yield* makeUuid4.pipe(Effect.provideService(GetRandomValues, getRandomValues));
192
183
  const to = { ...entries[nextIndex], id };
193
184
  const delta = nextIndex - index;
194
185
  const event = {
195
- type: "traverse",
186
+ type: 'traverse',
196
187
  from,
197
188
  to,
198
189
  delta,
199
- info: options?.info
190
+ info: options?.info,
200
191
  };
201
192
  return yield* runNavigationEvent(event, get, set, 0, skipCommit);
202
193
  }));
@@ -218,82 +209,61 @@ export function setupFromModelAndIntent(modelAndIntent, origin, base, getRandomV
218
209
  const { entries, index } = yield* state;
219
210
  const current = entries[index];
220
211
  const event = {
221
- type: "reload",
212
+ type: 'reload',
222
213
  from: current,
223
214
  to: current,
224
215
  delta: 0,
225
- info: options?.info
216
+ info: options?.info,
226
217
  };
227
218
  return yield* runNavigationEvent(event, get, set, 0, skipCommit);
228
219
  }));
229
220
  const beforeNavigation = (handler) => Effect.contextWithEffect((ctx) => {
230
221
  const entry = [handler, ctx];
231
- return Effect.zipRight(RefSubject.update(beforeHandlers, (handlers) => new Set([...handlers, entry])), Effect.addFinalizer(() => RefSubject.update(beforeHandlers, (handlers) => {
222
+ const acquire = LazyRef.update(beforeHandlers, (handlers) => new Set([...handlers, entry]));
223
+ const release = Effect.ignoreLogged(LazyRef.update(beforeHandlers, (handlers) => {
232
224
  const updated = new Set(handlers);
233
225
  updated.delete(entry);
234
226
  return updated;
235
- })));
227
+ }));
228
+ return Effect.acquireRelease(acquire, () => release);
236
229
  });
237
230
  const onNavigation = (handler) => Effect.contextWithEffect((ctx) => {
238
231
  const entry = [handler, ctx];
239
- return Effect.zipRight(RefSubject.update(handlers, (handlers) => new Set([...handlers, entry])), Effect.addFinalizer(() => RefSubject.update(handlers, (handlers) => {
232
+ const acquire = LazyRef.update(handlers, (handlers) => new Set([...handlers, entry]));
233
+ const release = Effect.ignoreLogged(LazyRef.update(handlers, (handlers) => {
240
234
  const updated = new Set(handlers);
241
235
  updated.delete(entry);
242
236
  return updated;
243
- })));
237
+ }));
238
+ return Effect.acquireRelease(acquire, () => release);
244
239
  });
245
240
  const updateCurrentEntry = (options) => state.runUpdates(({ get, set }) => Effect.gen(function* () {
246
241
  const { entries, index } = yield* get;
247
242
  const current = entries[index];
248
243
  const event = {
249
- type: "replace",
244
+ type: 'replace',
250
245
  from: current,
251
246
  to: { ...current, state: options.state },
252
247
  delta: 0,
253
- info: null
248
+ info: null,
254
249
  };
255
250
  return yield* runNavigationEvent(event, get, set, 0);
256
251
  }));
257
- const submit = (data, input) => state.runUpdates(({ get, set }) => Effect.gen(function* () {
258
- const { entries, index } = yield* get;
259
- const from = entries[index];
260
- const event = {
261
- from,
262
- data,
263
- name: Option.fromNullable(input?.name),
264
- action: Option.fromNullable(input?.action),
265
- method: Option.fromNullable(input?.method),
266
- encoding: Option.fromNullable(input?.encoding)
267
- };
268
- const either = yield* runFormDataHandlers(event);
269
- if (Either.isLeft(either)) {
270
- yield* handleError(either.left, get, set, 0);
271
- return Option.none();
272
- }
273
- else {
274
- if (Option.isNone(either.right)) {
275
- return either.right;
276
- }
277
- const response = either.right.value;
278
- // If it is a redirect
279
- if (REDIRECT_STATUS_CODES.has(response.status)) {
280
- const location = Headers.get(response.headers, "location");
281
- // And we have a location header
282
- if (Option.isSome(location)) {
283
- // Then we navigate to that location
284
- yield* navigate(location.value, { history: "replace" });
285
- }
252
+ const submit = (form) => Effect.gen(function* () {
253
+ const current = yield* currentEntry;
254
+ // Utilize the action URL if provided, otherwise use the current URL
255
+ const url = form.action ? getUrl(origin, form.action, base) : current.url;
256
+ // Submit the HTTP request
257
+ const response = yield* HttpClient[form.method](url, form).pipe(Effect.mapErrorCause((cause) => Cause.fail(new FormSubmitError({ cause }))));
258
+ // If the response is a redirect, navigate to the new URL
259
+ if (REDIRECT_STATUS_CODES.has(response.status)) {
260
+ // Get the location header
261
+ const location = Headers.get(response.headers, 'location');
262
+ if (Option.isSome(location)) {
263
+ return [yield* navigate(getUrl(origin, location.value, base), form), response];
286
264
  }
287
- return Option.some(response);
288
265
  }
289
- }));
290
- const onFormData = (handler) => Effect.contextWithEffect((ctx) => {
291
- const entry = [handler, ctx];
292
- return Effect.zipRight(RefSubject.update(formDataHandlers, (handlers) => new Set([...handlers, entry])), Effect.addFinalizer(() => RefSubject.update(formDataHandlers, (handlers) => {
293
- const updated = new Set(handlers);
294
- updated.delete(entry);
295
- return updated;
296
- })));
266
+ return [current, response];
297
267
  });
298
268
  const navigation = {
299
269
  back,
@@ -312,20 +282,19 @@ export function setupFromModelAndIntent(modelAndIntent, origin, base, getRandomV
312
282
  traverseTo,
313
283
  updateCurrentEntry,
314
284
  submit,
315
- onFormData
316
285
  };
317
286
  return navigation;
318
287
  }
319
- export function makeRedirectEvent(origin, redirect, from) {
288
+ function makeRedirectEvent(origin, redirect, from, base) {
320
289
  return Effect.gen(function* () {
321
- const url = getUrl(origin, redirect.path);
290
+ const url = getUrl(origin, redirect.path, base);
322
291
  const to = yield* makeDestination(url, redirect.options?.state, origin);
323
292
  const event = {
324
- type: "replace",
293
+ type: 'replace',
325
294
  from,
326
295
  to,
327
296
  delta: 0,
328
- info: redirect.options?.info
297
+ info: redirect.options?.info,
329
298
  };
330
299
  return event;
331
300
  });
@@ -333,16 +302,15 @@ export function makeRedirectEvent(origin, redirect, from) {
333
302
  export function makeOrUpdateDestination(navigationState, url, state, origin) {
334
303
  return Effect.gen(function* () {
335
304
  const current = navigationState.entries[navigationState.index];
336
- const isSameOriginAndPath = url.origin === current.url.origin &&
337
- url.pathname === current.url.pathname;
305
+ const isSameOriginAndPath = url.origin === current.url.origin && url.pathname === current.url.pathname;
338
306
  if (isSameOriginAndPath) {
339
- const id = yield* makeUuid;
307
+ const id = yield* makeUuid4;
340
308
  const destination = {
341
309
  id,
342
310
  key: current.key,
343
311
  url,
344
312
  state: getOriginalState(state),
345
- sameDocument: url.origin === origin
313
+ sameDocument: url.origin === origin,
346
314
  };
347
315
  return destination;
348
316
  }
@@ -359,42 +327,41 @@ export function makeDestination(url, state, origin) {
359
327
  key: state.__typed__navigation__key__,
360
328
  url,
361
329
  state: state.__typed__navigation__state__,
362
- sameDocument: url.origin === origin
330
+ sameDocument: url.origin === origin,
363
331
  };
364
332
  return destination;
365
333
  }
366
- const id = yield* makeUuid;
367
- const key = yield* makeUuid;
334
+ const id = yield* makeUuid4;
335
+ const key = yield* makeUuid4;
368
336
  const destination = {
369
337
  id,
370
338
  key,
371
339
  url,
372
340
  state,
373
- sameDocument: url.origin === origin
341
+ sameDocument: url.origin === origin,
374
342
  };
375
343
  return destination;
376
344
  });
377
345
  }
378
346
  export function upgradeProposedDestination(proposed) {
379
347
  return Effect.gen(function* () {
380
- const id = yield* makeUuid;
381
- const key = yield* makeUuid;
348
+ const id = yield* makeUuid4;
349
+ const key = yield* makeUuid4;
382
350
  const destination = {
383
351
  id,
384
352
  key,
385
353
  url: proposed.url,
386
354
  state: proposed.state,
387
- sameDocument: proposed.sameDocument
355
+ sameDocument: proposed.sameDocument,
388
356
  };
389
357
  return destination;
390
358
  });
391
359
  }
392
360
  export function isPatchedState(state) {
393
- if (state === null || !(typeof state === "object") || Array.isArray(state)) {
361
+ if (state === null || !(typeof state === 'object') || Array.isArray(state)) {
394
362
  return false;
395
363
  }
396
- if ("__typed__navigation__id__" in state &&
397
- "__typed__navigation__key__" in state) {
364
+ if ('__typed__navigation__id__' in state && '__typed__navigation__key__' in state) {
398
365
  return true;
399
366
  }
400
367
  return false;
@@ -406,7 +373,7 @@ export function getOriginalState(state) {
406
373
  }
407
374
  export function getOriginFromUrl(url) {
408
375
  try {
409
- if (typeof url === "string") {
376
+ if (typeof url === 'string') {
410
377
  return new URL(url).origin;
411
378
  }
412
379
  else {
@@ -414,33 +381,19 @@ export function getOriginFromUrl(url) {
414
381
  }
415
382
  }
416
383
  catch {
417
- return "http://localhost";
384
+ return 'http://localhost';
418
385
  }
419
386
  }
420
387
  export function isDestination(proposed) {
421
- return "id" in proposed && "key" in proposed;
388
+ return 'id' in proposed && 'key' in proposed;
422
389
  }
423
390
  const strictEqual = (a, b) => a === b;
424
- export function makeHandlersState() {
425
- return Effect.gen(function* () {
426
- const beforeHandlers = yield* RefSubject.fromEffect(Effect.sync(() => new Set()), { eq: strictEqual });
427
- const handlers = yield* RefSubject.fromEffect(Effect.sync(() => new Set()), { eq: strictEqual });
428
- const formDataHandlers = yield* RefSubject.fromEffect(Effect.sync(() => new Set()), { eq: strictEqual });
429
- return {
430
- beforeHandlers,
431
- handlers,
432
- formDataHandlers
433
- };
434
- });
435
- }
436
- function makeFormDataRequest(event, url) {
437
- const headers = new globalThis.Headers();
438
- if (Option.isSome(event.encoding)) {
439
- headers.set("Content-Type", event.encoding.value);
440
- }
441
- const method = Option.getOrElse(event.method, () => "POST");
442
- return Effect.flatMap(HttpClient.HttpClient, (client) => client.execute(HttpClientRequest.make(method)(url, {
443
- headers: Headers.fromInput(headers)
444
- }).pipe(HttpClientRequest.bodyFormData(event.data))));
445
- }
391
+ export const makeHandlersState = Effect.gen(function* () {
392
+ const beforeHandlers = yield* LazyRef.fromEffect(Effect.sync(() => new Set()), { eq: strictEqual });
393
+ const handlers = yield* LazyRef.fromEffect(Effect.sync(() => new Set()), { eq: strictEqual });
394
+ return {
395
+ beforeHandlers,
396
+ handlers,
397
+ };
398
+ });
446
399
  //# sourceMappingURL=shared.js.map