ui-router-effect 0.1.2 → 0.3.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/README.md +24 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/route-ref.d.ts +3 -2
- package/dist/route-ref.d.ts.map +1 -1
- package/dist/route-ref.js.map +1 -1
- package/dist/route-types.d.ts +34 -0
- package/dist/route-types.d.ts.map +1 -0
- package/dist/route-types.js +2 -0
- package/dist/route-types.js.map +1 -0
- package/dist/shared/transition-status.d.ts +33 -0
- package/dist/shared/transition-status.d.ts.map +1 -0
- package/dist/shared/transition-status.js +27 -0
- package/dist/shared/transition-status.js.map +1 -0
- package/dist/transition-refs.d.ts +28 -0
- package/dist/transition-refs.d.ts.map +1 -0
- package/dist/transition-refs.js +35 -0
- package/dist/transition-refs.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +4 -0
- package/src/route-ref.ts +6 -3
- package/src/route-types.ts +38 -0
- package/src/transition-refs.ts +84 -0
package/README.md
CHANGED
|
@@ -14,16 +14,18 @@ npm install ui-router-effect effect
|
|
|
14
14
|
|
|
15
15
|
## Features
|
|
16
16
|
|
|
17
|
-
- **`routeRef(router)`**: a `SubscriptionRef<
|
|
17
|
+
- **`routeRef(router)`**: a `SubscriptionRef<TypedRouteSnapshot>` of the current state, params and transition. There is one ref, and one `onSuccess` hook, per router, attached lazily on first use. Every value it holds is a settled route.
|
|
18
|
+
- **`pendingRef(router)`** and **`errorRef(router)`**: `SubscriptionRef`s of the newest transition, kept out of `RouteSnapshot`. Two refs, and one `onStart` hook, per router, attached lazily on first use of either. `pendingRef` holds the transition still in flight, cleared when that transition settles, however it settles, unless a newer one has started. `errorRef` holds that transition's `RejectType.ERROR` rejection, cleared when the next transition starts.
|
|
18
19
|
- **`RouteSnapshot`** and **`snapshotRoute(router, transition?)`**: the value the ref holds. Its `includes()` evaluates `StateService.includes` against the snapshot, not the live router.
|
|
20
|
+
- **`RouteTypes`**: an empty interface an app or a registry fills in by module augmentation. Its `snapshot` member types the ref's value, so a snapshot whose `includes(name)` is a type predicate narrows `current` and `params`. Empty, the ref holds a `RouteSnapshot`. `snapshotRoute` stays untyped.
|
|
19
21
|
|
|
20
22
|
## Usage
|
|
21
23
|
|
|
22
24
|
```typescript
|
|
23
|
-
import { Effect, Stream } from 'effect';
|
|
25
|
+
import { Effect, Stream, SubscriptionRef } from 'effect';
|
|
24
26
|
import { routeRef } from 'ui-router-effect';
|
|
25
27
|
|
|
26
|
-
const names = routeRef(router).
|
|
28
|
+
const names = SubscriptionRef.changes(routeRef(router)).pipe(
|
|
27
29
|
Stream.map((route) => route.current?.name),
|
|
28
30
|
Stream.changes,
|
|
29
31
|
);
|
|
@@ -31,6 +33,25 @@ const names = routeRef(router).changes.pipe(
|
|
|
31
33
|
Effect.runFork(Stream.runForEach(names, (name) => Effect.log(name)));
|
|
32
34
|
```
|
|
33
35
|
|
|
36
|
+
### Loading and error state
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { Effect, Stream, SubscriptionRef } from 'effect';
|
|
40
|
+
import { errorRef, pendingRef } from 'ui-router-effect';
|
|
41
|
+
|
|
42
|
+
const status = Stream.zipLatestWith(
|
|
43
|
+
SubscriptionRef.changes(pendingRef(router)),
|
|
44
|
+
SubscriptionRef.changes(errorRef(router)),
|
|
45
|
+
(pending, error) => (pending ? 'loading' : error ? 'error' : 'idle'),
|
|
46
|
+
).pipe(Stream.changes);
|
|
47
|
+
|
|
48
|
+
Effect.runFork(
|
|
49
|
+
Stream.runForEach(status, (value) =>
|
|
50
|
+
Effect.sync(() => (document.body.dataset.routeStatus = value)),
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
```
|
|
54
|
+
|
|
34
55
|
## Dependencies
|
|
35
56
|
|
|
36
57
|
`@uirouter/core` and `effect` are peer dependencies. `lit` and `lit-ui-router` are not, and the `bundleProbe` claim in `package.json` holds the entry free of them.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { errorRef, pendingRef } from "./transition-refs.js";
|
|
1
2
|
export { routeRef } from "./route-ref.js";
|
|
2
3
|
export { type RouteSnapshot, snapshotRoute } from "./shared/route-snapshot.js";
|
|
4
|
+
export { type RouteTypes, type TypedRouteSnapshot } from "./route-types.js";
|
|
3
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","mappings":"AAAA,SAAS,gBAAgB;AAEzB,cAAc,eAAe,qBAAqB","names":[],"ignoreList":[],"sources":["../src/index.ts"]}
|
|
1
|
+
{"version":3,"file":"index.d.ts","mappings":"AAAA,SAAS,UAAU,kBAAkB;AAErC,SAAS,gBAAgB;AAEzB,cAAc,eAAe,qBAAqB;AAElD,cAAc,iBAAiB,0BAA0B","names":[],"ignoreList":[],"sources":["../src/index.ts"]}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","mappings":"AAAA,SAAS,gBAAgB;AAEzB,SAA6B,qBAAqB","names":[],"ignoreList":[],"sources":["../src/index.ts"]}
|
|
1
|
+
{"version":3,"file":"index.js","mappings":"AAAA,SAAS,UAAU,kBAAkB;AAErC,SAAS,gBAAgB;AAEzB,SAA6B,qBAAqB","names":[],"ignoreList":[],"sources":["../src/index.ts"]}
|
package/dist/route-ref.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SubscriptionRef } from "effect";
|
|
2
2
|
import { UIRouter } from "@uirouter/core";
|
|
3
|
-
import {
|
|
3
|
+
import type { TypedRouteSnapshot } from "./route-types.js";
|
|
4
4
|
/**
|
|
5
5
|
* The route ref for a router — one per router instance, attached lazily.
|
|
6
6
|
*
|
|
@@ -8,6 +8,7 @@ import { RouteSnapshot } from "./shared/route-snapshot.js";
|
|
|
8
8
|
* ref and registers the single `onSuccess` hook that feeds it, and both live
|
|
9
9
|
* as long as the router does. `onSuccess` is the only hook a later transition
|
|
10
10
|
* cannot supersede, so every value the ref ever holds is a settled route.
|
|
11
|
+
* Its snapshot is typed by `RouteTypes` when a registry fills it in.
|
|
11
12
|
*/
|
|
12
|
-
export declare function routeRef(router: UIRouter): SubscriptionRef.SubscriptionRef<
|
|
13
|
+
export declare function routeRef(router: UIRouter): SubscriptionRef.SubscriptionRef<TypedRouteSnapshot>;
|
|
13
14
|
//# sourceMappingURL=route-ref.d.ts.map
|
package/dist/route-ref.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-ref.d.ts","mappings":"AAAA,SAAiB,uBAAuB;AACxC,SAAS,gBAAgB;
|
|
1
|
+
{"version":3,"file":"route-ref.d.ts","mappings":"AAAA,SAAiB,uBAAuB;AACxC,SAAS,gBAAgB;AAIzB,cAAc,0BAA0B;;;;;;;;;;AAgBxC,OAAO,iBAAS,SACd,QAAQ,WACP,gBAAgB,gBAAgB","names":[],"ignoreList":[],"sources":["../src/route-ref.ts"]}
|
package/dist/route-ref.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-ref.js","mappings":"AAAA,SAAS,QAAQ,uBAAuB;AAGxC,
|
|
1
|
+
{"version":3,"file":"route-ref.js","mappings":"AAAA,SAAS,QAAQ,uBAAuB;AAGxC,SAAS,qBAAqB;AAI9B,MAAM,YAAY,IAAI,QAGpB;AAWF,OAAO,SAAS,SACd,QACqD;CACrD,MAAM,WAAW,UAAU,IAAI,MAAM;CAErC,IAAI,UAAU,OAAO;CACrB,MAAM,MAAM,OAAO,QAAQ,gBAAgB,KAAK,cAAc,MAAM,CAAC,CAAC;CACtE,UAAU,IAAI,QAAQ,GAAG;CACzB,OAAO,kBAAkB,UAAU,CAAC,IAAI,eAAe;EACrD,OAAO,QAAQ,gBAAgB,IAAI,KAAK,cAAc,QAAQ,UAAU,CAAC,CAAC;CAC5E,CAAC;CAED,OAAO;AACT","names":[],"ignoreList":[],"sources":["../src/route-ref.ts"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { RouteSnapshot } from "./shared/route-snapshot.js";
|
|
2
|
+
/**
|
|
3
|
+
* Typed route snapshots, filled in by module augmentation. Empty, the
|
|
4
|
+
* snapshot is {@link RouteSnapshot}, as it always has been; nothing here
|
|
5
|
+
* emits JavaScript.
|
|
6
|
+
*
|
|
7
|
+
* An app or a registry such as `ui-router-registry` fills it in. Its member:
|
|
8
|
+
*
|
|
9
|
+
* - `snapshot`: the type `routeRef`'s ref holds, a typed view of
|
|
10
|
+
* {@link RouteSnapshot} whose `includes(name)` narrows `current` and
|
|
11
|
+
* `params`.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* declare module 'ui-router-effect' {
|
|
15
|
+
* interface RouteTypes {
|
|
16
|
+
* snapshot: AppRouteSnapshot;
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @category types
|
|
22
|
+
*/
|
|
23
|
+
export interface RouteTypes {}
|
|
24
|
+
/**
|
|
25
|
+
* The route snapshot as {@link RouteTypes} types it: its `snapshot` member,
|
|
26
|
+
* or {@link RouteSnapshot} until it is filled in.
|
|
27
|
+
*
|
|
28
|
+
* @category types
|
|
29
|
+
*/
|
|
30
|
+
export type TypedRouteSnapshot = RouteTypes extends {
|
|
31
|
+
/** The filled-in snapshot type. */
|
|
32
|
+
snapshot: infer S;
|
|
33
|
+
} ? S : RouteSnapshot;
|
|
34
|
+
//# sourceMappingURL=route-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-types.d.ts","mappings":"AAAA,cAAc,qBAAqB;;;;;;;;;;;;;;;;;;;;;;AAwBnC,iBAAiB,WAAW,CAAC;;;;;;;AAQ7B,YAAY,qBAAqB,mBAAmB;;CAElD,gBAAgB;AAClB,IACI,IACA","names":[],"ignoreList":[],"sources":["../src/route-types.ts"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-types.js","mappings":"","names":[],"ignoreList":[],"sources":["../src/route-types.ts"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Rejection, Transition, UIRouter } from "@uirouter/core";
|
|
2
|
+
/** The router's newest transition: in flight, or how it last failed. */
|
|
3
|
+
export interface TransitionStatus {
|
|
4
|
+
/** The newest transition still in flight; `undefined` when none is. */
|
|
5
|
+
readonly pending: Transition | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* The newest transition's `RejectType.ERROR` rejection; `undefined` while
|
|
8
|
+
* one is in flight, after a success, and after any other rejection.
|
|
9
|
+
*/
|
|
10
|
+
readonly error: Rejection | undefined;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Mirrors the status of the router's newest transition into `set`, one
|
|
14
|
+
* whole {@link TransitionStatus} per change: `{ pending: transition }` when
|
|
15
|
+
* it starts, then `{ pending: undefined, error }` once it settles, however it
|
|
16
|
+
* settles. `error` is the transition's `error()` only for a
|
|
17
|
+
* `RejectType.ERROR` rejection; superseded, redirected, aborted and ignored
|
|
18
|
+
* transitions leave it `undefined`. Invalid transitions are rejected before
|
|
19
|
+
* `onStart`, so they never reach the tracker.
|
|
20
|
+
*
|
|
21
|
+
* `onStart` records each transition as the newest. Its promise settles the
|
|
22
|
+
* status only while it is still the newest, so a superseded transition never
|
|
23
|
+
* clears or fails its successor. `onFinish` would miss failures: it never
|
|
24
|
+
* fires for a transition that errors or is aborted.
|
|
25
|
+
*
|
|
26
|
+
* A transition already running when this attaches is picked up too, so a
|
|
27
|
+
* mirror created mid-navigation reads it as pending.
|
|
28
|
+
*
|
|
29
|
+
* @returns the `onStart` hook's deregistration function. A transition
|
|
30
|
+
* already pending still settles when it does.
|
|
31
|
+
*/
|
|
32
|
+
export declare function trackTransitionStatus(router: UIRouter, set: (status: TransitionStatus) => void): () => void;
|
|
33
|
+
//# sourceMappingURL=transition-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transition-status.d.ts","mappings":"AAAA,SAAS,WAAuB,YAAY,gBAAgB;;AAG5D,iBAAiB,iBAAiB;;UAEvB,SAAS;;;;;UAKT,OAAO;AAClB;;;;;;;;;;;;;;;;;;;;;AAsBA,OAAO,iBAAS,sBACd,QAAQ,UACR,MAAM,QAAQ","names":[],"ignoreList":[],"sources":["../../../shared/transition-status/src/transition-status.ts"],"sourcesContent":["import { Rejection, RejectType, Transition, UIRouter } from '@uirouter/core';\n\n/** The router's newest transition: in flight, or how it last failed. */\nexport interface TransitionStatus {\n /** The newest transition still in flight; `undefined` when none is. */\n readonly pending: Transition | undefined;\n /**\n * The newest transition's `RejectType.ERROR` rejection; `undefined` while\n * one is in flight, after a success, and after any other rejection.\n */\n readonly error: Rejection | undefined;\n}\n\n/**\n * Mirrors the status of the router's newest transition into `set`, one\n * whole {@link TransitionStatus} per change: `{ pending: transition }` when\n * it starts, then `{ pending: undefined, error }` once it settles, however it\n * settles. `error` is the transition's `error()` only for a\n * `RejectType.ERROR` rejection; superseded, redirected, aborted and ignored\n * transitions leave it `undefined`. Invalid transitions are rejected before\n * `onStart`, so they never reach the tracker.\n *\n * `onStart` records each transition as the newest. Its promise settles the\n * status only while it is still the newest, so a superseded transition never\n * clears or fails its successor. `onFinish` would miss failures: it never\n * fires for a transition that errors or is aborted.\n *\n * A transition already running when this attaches is picked up too, so a\n * mirror created mid-navigation reads it as pending.\n *\n * @returns the `onStart` hook's deregistration function. A transition\n * already pending still settles when it does.\n */\nexport function trackTransitionStatus(\n router: UIRouter,\n set: (status: TransitionStatus) => void,\n): () => void {\n let newest: Transition | undefined;\n\n const start = (transition: Transition): void => {\n newest = transition;\n set({ pending: transition, error: undefined });\n\n const settle = (): void => {\n if (newest !== transition) return;\n newest = undefined;\n // SAFETY: `error()` is undefined after a success; core types it as `Rejection`.\n const error = transition.error() as Rejection | undefined;\n set({\n pending: undefined,\n error: error?.type === RejectType.ERROR ? error : undefined,\n });\n };\n\n // Handled here, the rejection still reaches the transition's own callers and onError hooks.\n void transition.promise.then(settle, settle);\n };\n\n const running = router.globals.transition;\n\n // SAFETY: `success` stays undefined until the transition settles; core types it as `boolean`.\n if (running && (running.success as boolean | undefined) === undefined) {\n start(running);\n }\n\n // SAFETY: `onStart` returns its deregistration function, typed only as `Function`.\n return router.transitionService.onStart({}, start) as () => void;\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { RejectType } from "@uirouter/core";
|
|
2
|
+
export function trackTransitionStatus(router, set) {
|
|
3
|
+
let newest;
|
|
4
|
+
const start = (transition) => {
|
|
5
|
+
newest = transition;
|
|
6
|
+
set({
|
|
7
|
+
pending: transition,
|
|
8
|
+
error: undefined
|
|
9
|
+
});
|
|
10
|
+
const settle = () => {
|
|
11
|
+
if (newest !== transition) return;
|
|
12
|
+
newest = undefined;
|
|
13
|
+
const error = transition.error();
|
|
14
|
+
set({
|
|
15
|
+
pending: undefined,
|
|
16
|
+
error: error?.type === RejectType.ERROR ? error : undefined
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
void transition.promise.then(settle, settle);
|
|
20
|
+
};
|
|
21
|
+
const running = router.globals.transition;
|
|
22
|
+
if (running && running.success === undefined) {
|
|
23
|
+
start(running);
|
|
24
|
+
}
|
|
25
|
+
return router.transitionService.onStart({}, start);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=transition-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transition-status.js","mappings":"AAAA,SAAoB,kBAAwC;AAiC5D,OAAO,SAAS,sBACd,QACA,KACY;CACZ,IAAI;CAEJ,MAAM,SAAS,eAAiC;EAC9C,SAAS;EACT,IAAI;GAAE,SAAS;GAAY,OAAO;EAAU,CAAC;EAE7C,MAAM,eAAqB;GACzB,IAAI,WAAW,YAAY;GAC3B,SAAS;GAET,MAAM,QAAQ,WAAW,MAAM;GAC/B,IAAI;IACF,SAAS;IACT,OAAO,OAAO,SAAS,WAAW,QAAQ,QAAQ;GACpD,CAAC;EACH;EAGA,KAAK,WAAW,QAAQ,KAAK,QAAQ,MAAM;CAC7C;CAEA,MAAM,UAAU,OAAO,QAAQ;CAG/B,IAAI,WAAY,QAAQ,YAAoC,WAAW;EACrE,MAAM,OAAO;CACf;CAGA,OAAO,OAAO,kBAAkB,QAAQ,CAAC,GAAG,KAAK;AACnD","names":[],"ignoreList":[],"sources":["../../../shared/transition-status/src/transition-status.ts"],"sourcesContent":["import { Rejection, RejectType, Transition, UIRouter } from '@uirouter/core';\n\n/** The router's newest transition: in flight, or how it last failed. */\nexport interface TransitionStatus {\n /** The newest transition still in flight; `undefined` when none is. */\n readonly pending: Transition | undefined;\n /**\n * The newest transition's `RejectType.ERROR` rejection; `undefined` while\n * one is in flight, after a success, and after any other rejection.\n */\n readonly error: Rejection | undefined;\n}\n\n/**\n * Mirrors the status of the router's newest transition into `set`, one\n * whole {@link TransitionStatus} per change: `{ pending: transition }` when\n * it starts, then `{ pending: undefined, error }` once it settles, however it\n * settles. `error` is the transition's `error()` only for a\n * `RejectType.ERROR` rejection; superseded, redirected, aborted and ignored\n * transitions leave it `undefined`. Invalid transitions are rejected before\n * `onStart`, so they never reach the tracker.\n *\n * `onStart` records each transition as the newest. Its promise settles the\n * status only while it is still the newest, so a superseded transition never\n * clears or fails its successor. `onFinish` would miss failures: it never\n * fires for a transition that errors or is aborted.\n *\n * A transition already running when this attaches is picked up too, so a\n * mirror created mid-navigation reads it as pending.\n *\n * @returns the `onStart` hook's deregistration function. A transition\n * already pending still settles when it does.\n */\nexport function trackTransitionStatus(\n router: UIRouter,\n set: (status: TransitionStatus) => void,\n): () => void {\n let newest: Transition | undefined;\n\n const start = (transition: Transition): void => {\n newest = transition;\n set({ pending: transition, error: undefined });\n\n const settle = (): void => {\n if (newest !== transition) return;\n newest = undefined;\n // SAFETY: `error()` is undefined after a success; core types it as `Rejection`.\n const error = transition.error() as Rejection | undefined;\n set({\n pending: undefined,\n error: error?.type === RejectType.ERROR ? error : undefined,\n });\n };\n\n // Handled here, the rejection still reaches the transition's own callers and onError hooks.\n void transition.promise.then(settle, settle);\n };\n\n const running = router.globals.transition;\n\n // SAFETY: `success` stays undefined until the transition settles; core types it as `boolean`.\n if (running && (running.success as boolean | undefined) === undefined) {\n start(running);\n }\n\n // SAFETY: `onStart` returns its deregistration function, typed only as `Function`.\n return router.transitionService.onStart({}, start) as () => void;\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { SubscriptionRef } from "effect";
|
|
2
|
+
import { Rejection, Transition, UIRouter } from "@uirouter/core";
|
|
3
|
+
/**
|
|
4
|
+
* The pending-transition ref for a router — one per router instance,
|
|
5
|
+
* attached lazily.
|
|
6
|
+
*
|
|
7
|
+
* It holds the newest transition still in flight, and `undefined` when none
|
|
8
|
+
* is. The first caller of this or {@link errorRef} creates both refs and
|
|
9
|
+
* registers the single `onStart` hook that feeds them; the transition's
|
|
10
|
+
* promise clears it when that transition settles, whether it succeeds,
|
|
11
|
+
* errors or is aborted, unless a newer one has started. A transition already
|
|
12
|
+
* running on first use counts as pending. Kept out of `RouteSnapshot`, whose
|
|
13
|
+
* every value is a settled route.
|
|
14
|
+
*/
|
|
15
|
+
export declare function pendingRef(router: UIRouter): SubscriptionRef.SubscriptionRef<Transition | undefined>;
|
|
16
|
+
/**
|
|
17
|
+
* The failed-transition ref for a router — one per router instance,
|
|
18
|
+
* attached lazily, sharing {@link pendingRef}'s `onStart` hook.
|
|
19
|
+
*
|
|
20
|
+
* It holds the newest transition's failure as core's `Rejection`, and
|
|
21
|
+
* `undefined` otherwise. Only a `RejectType.ERROR` rejection, such as a
|
|
22
|
+
* resolve that throws, sets it; superseded, redirected, aborted and ignored
|
|
23
|
+
* transitions leave it `undefined`, as does a success. The next transition
|
|
24
|
+
* clears it as it starts, and a superseded transition that fails late never
|
|
25
|
+
* sets it. Kept out of `RouteSnapshot`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function errorRef(router: UIRouter): SubscriptionRef.SubscriptionRef<Rejection | undefined>;
|
|
28
|
+
//# sourceMappingURL=transition-refs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transition-refs.d.ts","mappings":"AAAA,SAAiB,uBAAuB;AACxC,SAAS,WAAW,YAAY,gBAAgB;;;;;;;;;;;;;AA6DhD,OAAO,iBAAS,WACd,QAAQ,WACP,gBAAgB,gBAAgB;;;;;;;;;;;;AAenC,OAAO,iBAAS,SACd,QAAQ,WACP,gBAAgB,gBAAgB","names":[],"ignoreList":[],"sources":["../src/transition-refs.ts"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Effect, SubscriptionRef } from "effect";
|
|
2
|
+
import { trackTransitionStatus } from "./shared/transition-status.js";
|
|
3
|
+
const transitionRefs = new WeakMap();
|
|
4
|
+
function refsFor(router) {
|
|
5
|
+
const existing = transitionRefs.get(router);
|
|
6
|
+
if (existing) return existing;
|
|
7
|
+
const refs = {
|
|
8
|
+
pending: Effect.runSync(SubscriptionRef.make(undefined)),
|
|
9
|
+
error: Effect.runSync(SubscriptionRef.make(undefined))
|
|
10
|
+
};
|
|
11
|
+
let error;
|
|
12
|
+
const setError = (next) => {
|
|
13
|
+
if (next === error) return;
|
|
14
|
+
error = next;
|
|
15
|
+
Effect.runSync(SubscriptionRef.set(refs.error, next));
|
|
16
|
+
};
|
|
17
|
+
transitionRefs.set(router, refs);
|
|
18
|
+
trackTransitionStatus(router, (status) => {
|
|
19
|
+
if (status.pending) {
|
|
20
|
+
Effect.runSync(SubscriptionRef.set(refs.pending, status.pending));
|
|
21
|
+
setError(status.error);
|
|
22
|
+
} else {
|
|
23
|
+
setError(status.error);
|
|
24
|
+
Effect.runSync(SubscriptionRef.set(refs.pending, undefined));
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return refs;
|
|
28
|
+
}
|
|
29
|
+
export function pendingRef(router) {
|
|
30
|
+
return refsFor(router).pending;
|
|
31
|
+
}
|
|
32
|
+
export function errorRef(router) {
|
|
33
|
+
return refsFor(router).error;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=transition-refs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transition-refs.js","mappings":"AAAA,SAAS,QAAQ,uBAAuB;AAGxC,SAAS,6BAA6B;AAOtC,MAAM,iBAAiB,IAAI,QAAkC;AAG7D,SAAS,QAAQ,QAAkC;CACjD,MAAM,WAAW,eAAe,IAAI,MAAM;CAE1C,IAAI,UAAU,OAAO;CAErB,MAAM,OAAuB;EAC3B,SAAS,OAAO,QACd,gBAAgB,KAA6B,SAAS,CACxD;EACA,OAAO,OAAO,QACZ,gBAAgB,KAA4B,SAAS,CACvD;CACF;CAEA,IAAI;CAEJ,MAAM,YAAY,SAAsC;EACtD,IAAI,SAAS,OAAO;EACpB,QAAQ;EACR,OAAO,QAAQ,gBAAgB,IAAI,KAAK,OAAO,IAAI,CAAC;CACtD;CAEA,eAAe,IAAI,QAAQ,IAAI;CAE/B,sBAAsB,SAAS,WAAW;EACxC,IAAI,OAAO,SAAS;GAClB,OAAO,QAAQ,gBAAgB,IAAI,KAAK,SAAS,OAAO,OAAO,CAAC;GAChE,SAAS,OAAO,KAAK;EACvB,OAAO;GACL,SAAS,OAAO,KAAK;GACrB,OAAO,QAAQ,gBAAgB,IAAI,KAAK,SAAS,SAAS,CAAC;EAC7D;CACF,CAAC;CAED,OAAO;AACT;AAcA,OAAO,SAAS,WACd,QACyD;CACzD,OAAO,QAAQ,MAAM,CAAC,CAAC;AACzB;AAaA,OAAO,SAAS,SACd,QACwD;CACxD,OAAO,QAAQ,MAAM,CAAC,CAAC;AACzB","names":[],"ignoreList":[],"sources":["../src/transition-refs.ts"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ui-router-effect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Effect bindings for UI-Router: a framework-agnostic SubscriptionRef of the router's current state",
|
|
5
5
|
"homepage": "https://lit-ui-router.dev/packages/ui-router-effect",
|
|
6
6
|
"bugs": {
|
package/src/index.ts
CHANGED
package/src/route-ref.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Effect, SubscriptionRef } from 'effect';
|
|
2
2
|
import { UIRouter } from '@uirouter/core';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { snapshotRoute } from '@repo/route-snapshot';
|
|
5
|
+
|
|
6
|
+
import type { TypedRouteSnapshot } from './route-types.js';
|
|
5
7
|
|
|
6
8
|
const routeRefs = new WeakMap<
|
|
7
9
|
UIRouter,
|
|
8
|
-
SubscriptionRef.SubscriptionRef<
|
|
10
|
+
SubscriptionRef.SubscriptionRef<TypedRouteSnapshot>
|
|
9
11
|
>();
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -15,10 +17,11 @@ const routeRefs = new WeakMap<
|
|
|
15
17
|
* ref and registers the single `onSuccess` hook that feeds it, and both live
|
|
16
18
|
* as long as the router does. `onSuccess` is the only hook a later transition
|
|
17
19
|
* cannot supersede, so every value the ref ever holds is a settled route.
|
|
20
|
+
* Its snapshot is typed by `RouteTypes` when a registry fills it in.
|
|
18
21
|
*/
|
|
19
22
|
export function routeRef(
|
|
20
23
|
router: UIRouter,
|
|
21
|
-
): SubscriptionRef.SubscriptionRef<
|
|
24
|
+
): SubscriptionRef.SubscriptionRef<TypedRouteSnapshot> {
|
|
22
25
|
const existing = routeRefs.get(router);
|
|
23
26
|
|
|
24
27
|
if (existing) return existing;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { RouteSnapshot } from '@repo/route-snapshot';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Typed route snapshots, filled in by module augmentation. Empty, the
|
|
5
|
+
* snapshot is {@link RouteSnapshot}, as it always has been; nothing here
|
|
6
|
+
* emits JavaScript.
|
|
7
|
+
*
|
|
8
|
+
* An app or a registry such as `ui-router-registry` fills it in. Its member:
|
|
9
|
+
*
|
|
10
|
+
* - `snapshot`: the type `routeRef`'s ref holds, a typed view of
|
|
11
|
+
* {@link RouteSnapshot} whose `includes(name)` narrows `current` and
|
|
12
|
+
* `params`.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* declare module 'ui-router-effect' {
|
|
16
|
+
* interface RouteTypes {
|
|
17
|
+
* snapshot: AppRouteSnapshot;
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @category types
|
|
23
|
+
*/
|
|
24
|
+
// oxlint-disable-next-line typescript/no-empty-interface, typescript/no-empty-object-type -- filled in by module augmentation
|
|
25
|
+
export interface RouteTypes {}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The route snapshot as {@link RouteTypes} types it: its `snapshot` member,
|
|
29
|
+
* or {@link RouteSnapshot} until it is filled in.
|
|
30
|
+
*
|
|
31
|
+
* @category types
|
|
32
|
+
*/
|
|
33
|
+
export type TypedRouteSnapshot = RouteTypes extends {
|
|
34
|
+
/** The filled-in snapshot type. */
|
|
35
|
+
snapshot: infer S;
|
|
36
|
+
}
|
|
37
|
+
? S
|
|
38
|
+
: RouteSnapshot;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Effect, SubscriptionRef } from 'effect';
|
|
2
|
+
import { Rejection, Transition, UIRouter } from '@uirouter/core';
|
|
3
|
+
|
|
4
|
+
import { trackTransitionStatus } from '@repo/transition-status';
|
|
5
|
+
|
|
6
|
+
interface TransitionRefs {
|
|
7
|
+
readonly pending: SubscriptionRef.SubscriptionRef<Transition | undefined>;
|
|
8
|
+
readonly error: SubscriptionRef.SubscriptionRef<Rejection | undefined>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const transitionRefs = new WeakMap<UIRouter, TransitionRefs>();
|
|
12
|
+
|
|
13
|
+
/** Both refs for a router, fed by one `onStart` hook registered on first use. */
|
|
14
|
+
function refsFor(router: UIRouter): TransitionRefs {
|
|
15
|
+
const existing = transitionRefs.get(router);
|
|
16
|
+
|
|
17
|
+
if (existing) return existing;
|
|
18
|
+
|
|
19
|
+
const refs: TransitionRefs = {
|
|
20
|
+
pending: Effect.runSync(
|
|
21
|
+
SubscriptionRef.make<Transition | undefined>(undefined),
|
|
22
|
+
),
|
|
23
|
+
error: Effect.runSync(
|
|
24
|
+
SubscriptionRef.make<Rejection | undefined>(undefined),
|
|
25
|
+
),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let error: Rejection | undefined;
|
|
29
|
+
|
|
30
|
+
const setError = (next: Rejection | undefined): void => {
|
|
31
|
+
if (next === error) return;
|
|
32
|
+
error = next;
|
|
33
|
+
Effect.runSync(SubscriptionRef.set(refs.error, next));
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
transitionRefs.set(router, refs);
|
|
37
|
+
// `pending` is set before `error` clears, and `error` before `pending` clears, so no read between the two looks idle.
|
|
38
|
+
trackTransitionStatus(router, (status) => {
|
|
39
|
+
if (status.pending) {
|
|
40
|
+
Effect.runSync(SubscriptionRef.set(refs.pending, status.pending));
|
|
41
|
+
setError(status.error);
|
|
42
|
+
} else {
|
|
43
|
+
setError(status.error);
|
|
44
|
+
Effect.runSync(SubscriptionRef.set(refs.pending, undefined));
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return refs;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The pending-transition ref for a router — one per router instance,
|
|
53
|
+
* attached lazily.
|
|
54
|
+
*
|
|
55
|
+
* It holds the newest transition still in flight, and `undefined` when none
|
|
56
|
+
* is. The first caller of this or {@link errorRef} creates both refs and
|
|
57
|
+
* registers the single `onStart` hook that feeds them; the transition's
|
|
58
|
+
* promise clears it when that transition settles, whether it succeeds,
|
|
59
|
+
* errors or is aborted, unless a newer one has started. A transition already
|
|
60
|
+
* running on first use counts as pending. Kept out of `RouteSnapshot`, whose
|
|
61
|
+
* every value is a settled route.
|
|
62
|
+
*/
|
|
63
|
+
export function pendingRef(
|
|
64
|
+
router: UIRouter,
|
|
65
|
+
): SubscriptionRef.SubscriptionRef<Transition | undefined> {
|
|
66
|
+
return refsFor(router).pending;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The failed-transition ref for a router — one per router instance,
|
|
71
|
+
* attached lazily, sharing {@link pendingRef}'s `onStart` hook.
|
|
72
|
+
*
|
|
73
|
+
* It holds the newest transition's failure as core's `Rejection`, and
|
|
74
|
+
* `undefined` otherwise. Only a `RejectType.ERROR` rejection, such as a
|
|
75
|
+
* resolve that throws, sets it; superseded, redirected, aborted and ignored
|
|
76
|
+
* transitions leave it `undefined`, as does a success. The next transition
|
|
77
|
+
* clears it as it starts, and a superseded transition that fails late never
|
|
78
|
+
* sets it. Kept out of `RouteSnapshot`.
|
|
79
|
+
*/
|
|
80
|
+
export function errorRef(
|
|
81
|
+
router: UIRouter,
|
|
82
|
+
): SubscriptionRef.SubscriptionRef<Rejection | undefined> {
|
|
83
|
+
return refsFor(router).error;
|
|
84
|
+
}
|