@kestack/kabuki 0.0.1 → 0.0.2
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 +156 -33
- package/dist/core/actor/actor.d.ts +24 -13
- package/dist/core/actor/actor.d.ts.map +1 -1
- package/dist/core/actor/actor.js +37 -26
- package/dist/core/actor/index.d.ts +2 -2
- package/dist/core/actor/index.d.ts.map +1 -1
- package/dist/core/actor/types.d.ts +91 -47
- package/dist/core/actor/types.d.ts.map +1 -1
- package/dist/core/history/history.d.ts +11 -3
- package/dist/core/history/history.d.ts.map +1 -1
- package/dist/core/history/history.js +106 -27
- package/dist/core/history/replay.d.ts +2 -0
- package/dist/core/history/replay.d.ts.map +1 -1
- package/dist/core/history/replay.js +1 -0
- package/dist/core/history/saved.d.ts +1 -1
- package/dist/core/history/saved.d.ts.map +1 -1
- package/dist/core/history/saved.js +13 -9
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/snapshot/invoke.d.ts +2 -3
- package/dist/core/snapshot/invoke.d.ts.map +1 -1
- package/dist/core/snapshot/invoke.js +42 -49
- package/dist/core/snapshot/restore.js +8 -7
- package/dist/core/snapshot/tree.d.ts +1 -1
- package/dist/core/snapshot/tree.d.ts.map +1 -1
- package/dist/core/snapshot/tree.js +1 -1
- package/dist/react/app.d.ts +30 -3
- package/dist/react/app.d.ts.map +1 -1
- package/dist/react/app.js +119 -40
- package/dist/react/drawn.d.ts +1 -1
- package/dist/react/drawn.d.ts.map +1 -1
- package/dist/react/drawn.js +2 -1
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -0
- package/dist/react/lazy.d.ts +30 -0
- package/dist/react/lazy.d.ts.map +1 -0
- package/dist/react/lazy.js +56 -0
- package/dist/react/live.d.ts +7 -0
- package/dist/react/live.d.ts.map +1 -0
- package/dist/react/live.js +13 -0
- package/dist/react/view.d.ts +26 -6
- package/dist/react/view.d.ts.map +1 -1
- package/dist/react/view.js +17 -5
- package/dist/routing/adapter/adapter.d.ts +38 -0
- package/dist/routing/adapter/adapter.d.ts.map +1 -0
- package/dist/routing/adapter/adapter.js +10 -0
- package/dist/routing/adapter/browser/browser.d.ts +8 -0
- package/dist/routing/adapter/browser/browser.d.ts.map +1 -0
- package/dist/routing/adapter/browser/browser.js +33 -0
- package/dist/routing/adapter/browser/index.d.ts +2 -0
- package/dist/routing/adapter/browser/index.d.ts.map +1 -0
- package/dist/routing/adapter/browser/index.js +1 -0
- package/dist/routing/adapter/index.d.ts +3 -0
- package/dist/routing/adapter/index.d.ts.map +1 -0
- package/dist/routing/adapter/index.js +1 -0
- package/dist/routing/adapter/tanstack/index.d.ts +3 -0
- package/dist/routing/adapter/tanstack/index.d.ts.map +1 -0
- package/dist/routing/adapter/tanstack/index.js +1 -0
- package/dist/routing/adapter/tanstack/tanstack.d.ts +27 -0
- package/dist/routing/adapter/tanstack/tanstack.d.ts.map +1 -0
- package/dist/routing/adapter/tanstack/tanstack.js +38 -0
- package/dist/routing/capability/cache.d.ts +12 -0
- package/dist/routing/capability/cache.d.ts.map +1 -0
- package/dist/routing/capability/cache.js +170 -0
- package/dist/routing/capability/index.d.ts +6 -0
- package/dist/routing/capability/index.d.ts.map +1 -0
- package/dist/routing/capability/index.js +3 -0
- package/dist/routing/capability/loading.d.ts +82 -0
- package/dist/routing/capability/loading.d.ts.map +1 -0
- package/dist/routing/capability/loading.js +53 -0
- package/dist/routing/capability/navigation.d.ts +66 -0
- package/dist/routing/capability/navigation.d.ts.map +1 -0
- package/dist/routing/capability/navigation.js +65 -0
- package/dist/routing/index.d.ts +10 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +4 -0
- package/dist/routing/page/index.d.ts +5 -0
- package/dist/routing/page/index.d.ts.map +1 -0
- package/dist/routing/page/index.js +2 -0
- package/dist/routing/page/page.d.ts +151 -0
- package/dist/routing/page/page.d.ts.map +1 -0
- package/dist/routing/page/page.js +121 -0
- package/dist/routing/page/status.d.ts +121 -0
- package/dist/routing/page/status.d.ts.map +1 -0
- package/dist/routing/page/status.js +69 -0
- package/dist/routing/route/index.d.ts +11 -0
- package/dist/routing/route/index.d.ts.map +1 -0
- package/dist/routing/route/index.js +6 -0
- package/dist/routing/route/loader.d.ts +98 -0
- package/dist/routing/route/loader.d.ts.map +1 -0
- package/dist/routing/route/loader.js +17 -0
- package/dist/routing/route/metadata.d.ts +44 -0
- package/dist/routing/route/metadata.d.ts.map +1 -0
- package/dist/routing/route/metadata.js +53 -0
- package/dist/routing/route/node.d.ts +162 -0
- package/dist/routing/route/node.d.ts.map +1 -0
- package/dist/routing/route/node.js +3 -0
- package/dist/routing/route/places.d.ts +15 -0
- package/dist/routing/route/places.d.ts.map +1 -0
- package/dist/routing/route/places.js +87 -0
- package/dist/routing/route/route.d.ts +53 -0
- package/dist/routing/route/route.d.ts.map +1 -0
- package/dist/routing/route/route.js +148 -0
- package/dist/routing/route/url.d.ts +13 -0
- package/dist/routing/route/url.d.ts.map +1 -0
- package/dist/routing/route/url.js +79 -0
- package/dist/routing/route/values.d.ts +12 -0
- package/dist/routing/route/values.d.ts.map +1 -0
- package/dist/routing/route/values.js +47 -0
- package/dist/routing/router/context.d.ts +123 -0
- package/dist/routing/router/context.d.ts.map +1 -0
- package/dist/routing/router/context.js +156 -0
- package/dist/routing/router/data.d.ts +117 -0
- package/dist/routing/router/data.d.ts.map +1 -0
- package/dist/routing/router/data.js +247 -0
- package/dist/routing/router/define.d.ts +52 -0
- package/dist/routing/router/define.d.ts.map +1 -0
- package/dist/routing/router/define.js +48 -0
- package/dist/routing/router/frame.d.ts +10 -0
- package/dist/routing/router/frame.d.ts.map +1 -0
- package/dist/routing/router/frame.js +10 -0
- package/dist/routing/router/index.d.ts +7 -0
- package/dist/routing/router/index.d.ts.map +1 -0
- package/dist/routing/router/index.js +3 -0
- package/dist/routing/router/router.d.ts +165 -0
- package/dist/routing/router/router.d.ts.map +1 -0
- package/dist/routing/router/router.js +801 -0
- package/dist/routing/routing.d.ts +72 -0
- package/dist/routing/routing.d.ts.map +1 -0
- package/dist/routing/routing.js +53 -0
- package/dist/routing/scroll/index.d.ts +4 -0
- package/dist/routing/scroll/index.d.ts.map +1 -0
- package/dist/routing/scroll/index.js +2 -0
- package/dist/routing/scroll/script.d.ts +6 -0
- package/dist/routing/scroll/script.d.ts.map +1 -0
- package/dist/routing/scroll/script.js +52 -0
- package/dist/routing/scroll/scroll.d.ts +75 -0
- package/dist/routing/scroll/scroll.d.ts.map +1 -0
- package/dist/routing/scroll/scroll.js +201 -0
- package/dist/routing/scroll/selector.d.ts +14 -0
- package/dist/routing/scroll/selector.d.ts.map +1 -0
- package/dist/routing/scroll/selector.js +40 -0
- package/dist/routing/scroll/session.d.ts +6 -0
- package/dist/routing/scroll/session.d.ts.map +1 -0
- package/dist/routing/scroll/session.js +23 -0
- package/dist/start/client/client.d.ts +11 -0
- package/dist/start/client/client.d.ts.map +1 -0
- package/dist/start/client/client.js +24 -0
- package/dist/start/client/index.d.ts +3 -0
- package/dist/start/client/index.d.ts.map +1 -0
- package/dist/start/client/index.js +2 -0
- package/dist/start/client/rpc.d.ts +14 -0
- package/dist/start/client/rpc.d.ts.map +1 -0
- package/dist/start/client/rpc.js +70 -0
- package/dist/start/common/app.d.ts +66 -0
- package/dist/start/common/app.d.ts.map +1 -0
- package/dist/start/common/app.js +23 -0
- package/dist/start/common/common.d.ts +26 -0
- package/dist/start/common/common.d.ts.map +1 -0
- package/dist/start/common/common.js +19 -0
- package/dist/start/common/document.d.ts +44 -0
- package/dist/start/common/document.d.ts.map +1 -0
- package/dist/start/common/document.js +58 -0
- package/dist/start/common/index.d.ts +9 -0
- package/dist/start/common/index.d.ts.map +1 -0
- package/dist/start/common/index.js +5 -0
- package/dist/start/common/page.d.ts +67 -0
- package/dist/start/common/page.d.ts.map +1 -0
- package/dist/start/common/page.js +29 -0
- package/dist/start/common/wire.d.ts +37 -0
- package/dist/start/common/wire.d.ts.map +1 -0
- package/dist/start/common/wire.js +73 -0
- package/dist/start/platform/cloudflare/cloudflare.d.ts +8 -0
- package/dist/start/platform/cloudflare/cloudflare.d.ts.map +1 -0
- package/dist/start/platform/cloudflare/cloudflare.js +31 -0
- package/dist/start/platform/cloudflare/index.d.ts +2 -0
- package/dist/start/platform/cloudflare/index.d.ts.map +1 -0
- package/dist/start/platform/cloudflare/index.js +1 -0
- package/dist/start/platform/index.d.ts +2 -0
- package/dist/start/platform/index.d.ts.map +1 -0
- package/dist/start/platform/index.js +1 -0
- package/dist/start/platform/platform.d.ts +31 -0
- package/dist/start/platform/platform.d.ts.map +1 -0
- package/dist/start/platform/platform.js +13 -0
- package/dist/start/server/cache.d.ts +4 -0
- package/dist/start/server/cache.d.ts.map +1 -0
- package/dist/start/server/cache.js +26 -0
- package/dist/start/server/draw.d.ts +10 -0
- package/dist/start/server/draw.d.ts.map +1 -0
- package/dist/start/server/draw.js +75 -0
- package/dist/start/server/index.d.ts +3 -0
- package/dist/start/server/index.d.ts.map +1 -0
- package/dist/start/server/index.js +2 -0
- package/dist/start/server/layers.d.ts +97 -0
- package/dist/start/server/layers.d.ts.map +1 -0
- package/dist/start/server/layers.js +37 -0
- package/dist/start/server/respond.d.ts +15 -0
- package/dist/start/server/respond.d.ts.map +1 -0
- package/dist/start/server/respond.js +35 -0
- package/dist/start/server/rpc.d.ts +11 -0
- package/dist/start/server/rpc.d.ts.map +1 -0
- package/dist/start/server/rpc.js +51 -0
- package/dist/start/server/server.d.ts +13 -0
- package/dist/start/server/server.d.ts.map +1 -0
- package/dist/start/server/server.js +138 -0
- package/dist/start/server/tiers.d.ts +14 -0
- package/dist/start/server/tiers.d.ts.map +1 -0
- package/dist/start/server/tiers.js +31 -0
- package/dist/start/vite/dev.d.ts +5 -0
- package/dist/start/vite/dev.d.ts.map +1 -0
- package/dist/start/vite/dev.js +56 -0
- package/dist/start/vite/entries.d.ts +27 -0
- package/dist/start/vite/entries.d.ts.map +1 -0
- package/dist/start/vite/entries.js +66 -0
- package/dist/start/vite/files.d.ts +22 -0
- package/dist/start/vite/files.d.ts.map +1 -0
- package/dist/start/vite/files.js +23 -0
- package/dist/start/vite/index.d.ts +2 -0
- package/dist/start/vite/index.d.ts.map +1 -0
- package/dist/start/vite/index.js +1 -0
- package/dist/start/vite/lazy.d.ts +4 -0
- package/dist/start/vite/lazy.d.ts.map +1 -0
- package/dist/start/vite/lazy.js +30 -0
- package/dist/start/vite/manifest.d.ts +18 -0
- package/dist/start/vite/manifest.d.ts.map +1 -0
- package/dist/start/vite/manifest.js +42 -0
- package/dist/start/vite/styles.d.ts +7 -0
- package/dist/start/vite/styles.d.ts.map +1 -0
- package/dist/start/vite/styles.js +58 -0
- package/dist/start/vite/vite.d.ts +11 -0
- package/dist/start/vite/vite.d.ts.map +1 -0
- package/dist/start/vite/vite.js +217 -0
- package/package.json +53 -3
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, lazy as reactLazy, use } from 'react';
|
|
3
|
+
/** Told of each lazy View a drawing draws; given by the App's `onLazyView`. */
|
|
4
|
+
export const LazyContext = createContext(undefined);
|
|
5
|
+
// 4. Find
|
|
6
|
+
const byActor = new WeakMap();
|
|
7
|
+
/** The lazy View made for `actor`, if one was: to load its code before it is drawn. */
|
|
8
|
+
export const lazyOf = (actor) => byActor.get(actor.definition);
|
|
9
|
+
const KEY = Symbol.for('@kestack/kabuki/lazy-views');
|
|
10
|
+
const byId = () => {
|
|
11
|
+
const global = globalThis;
|
|
12
|
+
return (global[KEY] ??= new Map());
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* A View of `actor` loaded when first drawn: `load` imports a module whose
|
|
16
|
+
* default export is the View, made with `View.make`. The Actor is named here
|
|
17
|
+
* so the View can be found by it, and preloaded, before its code is there.
|
|
18
|
+
*/
|
|
19
|
+
export const lazy = (actor, load, options = {}) => {
|
|
20
|
+
// 1. Load
|
|
21
|
+
let view;
|
|
22
|
+
let loading;
|
|
23
|
+
const preload = () => (loading ??= load().then((module) => {
|
|
24
|
+
view = module.default;
|
|
25
|
+
}, (error) => {
|
|
26
|
+
loading = undefined;
|
|
27
|
+
throw error;
|
|
28
|
+
}));
|
|
29
|
+
const Loading = reactLazy(() => preload().then(() => ({ default: view })));
|
|
30
|
+
// 2. Draw
|
|
31
|
+
const Lazy = ((props) => {
|
|
32
|
+
// 3. Collect
|
|
33
|
+
use(LazyContext)?.(self);
|
|
34
|
+
const Loaded = view;
|
|
35
|
+
return Loaded ? _jsx(Loaded, { ...props }) : _jsx(Loading, { ...props });
|
|
36
|
+
});
|
|
37
|
+
const self = Object.defineProperties(Lazy, {
|
|
38
|
+
actor: { value: actor, enumerable: true },
|
|
39
|
+
id: { value: options.id, enumerable: true },
|
|
40
|
+
preload: { value: preload, enumerable: true },
|
|
41
|
+
loaded: { get: () => view !== undefined, enumerable: true },
|
|
42
|
+
displayName: { get: () => `Lazy(${actor.name}View)` },
|
|
43
|
+
});
|
|
44
|
+
if (options.id !== undefined)
|
|
45
|
+
byId().set(options.id, self);
|
|
46
|
+
byActor.set(actor.definition, self);
|
|
47
|
+
return self;
|
|
48
|
+
};
|
|
49
|
+
/** Preload every lazy View made with one of these ids; an id no lazy View was made with is skipped. */
|
|
50
|
+
export const preload = async (ids) => {
|
|
51
|
+
const views = byId();
|
|
52
|
+
await Promise.all([...ids].flatMap((id) => {
|
|
53
|
+
const view = views.get(id);
|
|
54
|
+
return view ? [view.preload()] : [];
|
|
55
|
+
}));
|
|
56
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the app is drawn as it is now, not at a past Step, as Time Travel
|
|
3
|
+
* shows it: what an effect that touches the page around the app, like its
|
|
4
|
+
* scroll, reads first. Outside an app, always; on a server, never.
|
|
5
|
+
*/
|
|
6
|
+
export declare const useLive: () => boolean;
|
|
7
|
+
//# sourceMappingURL=live.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../src/react/live.ts"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,eAAO,MAAM,OAAO,QAAO,OAO1B,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { use, useSyncExternalStore } from 'react';
|
|
2
|
+
import { AppContext } from './view.js';
|
|
3
|
+
const never = () => false;
|
|
4
|
+
const noop = () => () => { };
|
|
5
|
+
/**
|
|
6
|
+
* Whether the app is drawn as it is now, not at a past Step, as Time Travel
|
|
7
|
+
* shows it: what an effect that touches the page around the app, like its
|
|
8
|
+
* scroll, reads first. Outside an app, always; on a server, never.
|
|
9
|
+
*/
|
|
10
|
+
export const useLive = () => {
|
|
11
|
+
const drawn = use(AppContext);
|
|
12
|
+
return useSyncExternalStore(drawn?.subscribe ?? noop, drawn?.live ?? (() => true), never);
|
|
13
|
+
};
|
package/dist/react/view.d.ts
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import type { CSSProperties, ReactNode, Ref } from 'react';
|
|
2
|
-
import type { AnyActor,
|
|
2
|
+
import type { AnyActor, Only, Types } from '../core/index.ts';
|
|
3
|
+
import { lazy, preload } from './lazy.tsx';
|
|
3
4
|
/** One Instance in the tree, to draw: its ID and, for a keyed Child, its key. Never changes. */
|
|
4
5
|
export interface Handle<A> {
|
|
5
6
|
readonly id: string;
|
|
6
7
|
readonly key?: string;
|
|
7
8
|
readonly actor?: A;
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
+
/** A Child as its View finds it: a list of `each`, one that may be absent `while` its test is false, else one always there. */
|
|
11
|
+
type HandleOf<C> = C extends {
|
|
12
|
+
readonly actor: infer A;
|
|
13
|
+
} ? C extends {
|
|
14
|
+
readonly each: Function;
|
|
15
|
+
} ? ReadonlyArray<Handle<A> & {
|
|
10
16
|
readonly key: string;
|
|
11
|
-
}> :
|
|
17
|
+
}> : C extends {
|
|
18
|
+
readonly while: Function;
|
|
19
|
+
} ? Handle<A> | undefined : Handle<A> : never;
|
|
12
20
|
type KidsIn<A, T> = T extends keyof Types<A>['children'] ? Types<A>['children'][T] : {};
|
|
13
21
|
/**
|
|
14
22
|
* What a parent may hand the root element of a View: the `ref` and `style`
|
|
@@ -48,6 +56,8 @@ export interface DrawnApp {
|
|
|
48
56
|
readonly instance: (id: string) => Drawable | undefined;
|
|
49
57
|
/** The Instance as first drawn, before any Runtime: what a server draws and the client hydrates against. */
|
|
50
58
|
readonly served: (id: string) => Drawable | undefined;
|
|
59
|
+
/** Whether it draws the app as it is now, not a past Step: what may touch the page around it. */
|
|
60
|
+
readonly live: () => boolean;
|
|
51
61
|
}
|
|
52
62
|
export declare const AppContext: import("react").Context<DrawnApp | undefined>;
|
|
53
63
|
/**
|
|
@@ -55,16 +65,20 @@ export declare const AppContext: import("react").Context<DrawnApp | undefined>;
|
|
|
55
65
|
* `ref` and `style` to animate it by. Data from the parent is its Props, which
|
|
56
66
|
* the parent Actor hands it, so Update and Workers see it too.
|
|
57
67
|
*/
|
|
58
|
-
export type ViewOf<A> = (props: {
|
|
68
|
+
export type ViewOf<A> = ((props: {
|
|
59
69
|
readonly instance: Handle<A>;
|
|
60
70
|
readonly ref?: Ref<Element> | undefined;
|
|
61
71
|
readonly style?: CSSProperties | undefined;
|
|
62
|
-
}) => ReactNode
|
|
72
|
+
}) => ReactNode) & {
|
|
73
|
+
/** The Actor it draws. */
|
|
74
|
+
readonly actor: A;
|
|
75
|
+
};
|
|
63
76
|
/**
|
|
64
77
|
* How one Actor is drawn: one function of its Instance. The result re-renders
|
|
65
78
|
* only when its own Instance's Model, State, Props or set of Children changes,
|
|
66
79
|
* or the parent hands it a new `ref` or `style`, never because a descendant
|
|
67
|
-
* did.
|
|
80
|
+
* did. A new Instance in the same place, as a Child with a new key, is drawn
|
|
81
|
+
* afresh: no React state carries over from the one before.
|
|
68
82
|
*
|
|
69
83
|
* Once the Instance stops, the View keeps drawing its last data, so an exit
|
|
70
84
|
* animation still has something to show.
|
|
@@ -73,8 +87,14 @@ export type ViewOf<A> = (props: {
|
|
|
73
87
|
* the View a new `style` object on every render, with the same values.
|
|
74
88
|
*/
|
|
75
89
|
declare const make: <A extends AnyActor>(actor: A, draw: (self: ViewProps<A>) => ReactNode) => ViewOf<A>;
|
|
90
|
+
/**
|
|
91
|
+
* `make` draws an Actor; `lazy` loads a View made with `make` when it is
|
|
92
|
+
* first drawn; `preload` loads the lazy Views made with these ids.
|
|
93
|
+
*/
|
|
76
94
|
export declare const View: {
|
|
77
95
|
make: typeof make;
|
|
96
|
+
lazy: typeof lazy;
|
|
97
|
+
preload: typeof preload;
|
|
78
98
|
};
|
|
79
99
|
export {};
|
|
80
100
|
//# sourceMappingURL=view.d.ts.map
|
package/dist/react/view.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../src/react/view.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../src/react/view.tsx"],"names":[],"mappings":"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"}
|
package/dist/react/view.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
2
|
import { createContext, memo, use, useRef, useSyncExternalStore } from 'react';
|
|
3
|
+
import { lazy, preload } from './lazy.js';
|
|
2
4
|
export const AppContext = createContext(undefined);
|
|
3
5
|
/**
|
|
4
6
|
* How one Actor is drawn: one function of its Instance. The result re-renders
|
|
5
7
|
* only when its own Instance's Model, State, Props or set of Children changes,
|
|
6
8
|
* or the parent hands it a new `ref` or `style`, never because a descendant
|
|
7
|
-
* did.
|
|
9
|
+
* did. A new Instance in the same place, as a Child with a new key, is drawn
|
|
10
|
+
* afresh: no React state carries over from the one before.
|
|
8
11
|
*
|
|
9
12
|
* Once the Instance stops, the View keeps drawing its last data, so an exit
|
|
10
13
|
* animation still has something to show.
|
|
@@ -13,7 +16,7 @@ export const AppContext = createContext(undefined);
|
|
|
13
16
|
* the View a new `style` object on every render, with the same values.
|
|
14
17
|
*/
|
|
15
18
|
const make = (actor, draw) => {
|
|
16
|
-
const
|
|
19
|
+
const Drawn = ({ instance, ref, style }) => {
|
|
17
20
|
const drawn = use(AppContext);
|
|
18
21
|
if (!drawn)
|
|
19
22
|
throw new Error(`[@kestack/kabuki] ${actor.name}View is drawn outside its app`);
|
|
@@ -31,8 +34,13 @@ const make = (actor, draw) => {
|
|
|
31
34
|
...self,
|
|
32
35
|
root: { ref: ref, style },
|
|
33
36
|
});
|
|
34
|
-
}
|
|
35
|
-
View
|
|
37
|
+
};
|
|
38
|
+
const View = memo((given) => (_jsx(Drawn, { ...given }, given.instance.id)), same);
|
|
39
|
+
// Read when shown, so a name given after the View is made still counts.
|
|
40
|
+
Object.defineProperties(View, {
|
|
41
|
+
actor: { value: actor, enumerable: true },
|
|
42
|
+
displayName: { get: () => `${actor.name}View`, configurable: true },
|
|
43
|
+
});
|
|
36
44
|
return View;
|
|
37
45
|
};
|
|
38
46
|
/** What the parent hands is the same when each is the same value, and `style` when each of its values is. */
|
|
@@ -44,4 +52,8 @@ const same = (a, b) => {
|
|
|
44
52
|
(key === 'style' && sameValues(a[key], b[key])));
|
|
45
53
|
};
|
|
46
54
|
const sameValues = (a, b) => a !== undefined && b !== undefined && same(a, b);
|
|
47
|
-
|
|
55
|
+
/**
|
|
56
|
+
* `make` draws an Actor; `lazy` loads a View made with `make` when it is
|
|
57
|
+
* first drawn; `preload` loads the lazy Views made with these ids.
|
|
58
|
+
*/
|
|
59
|
+
export const View = { make, lazy, preload };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Context } from 'effect';
|
|
2
|
+
/**
|
|
3
|
+
* Where an app's Address lives, besides memory: the browser's own history or
|
|
4
|
+
* TanStack Router's. Every URL here is a whole path with its query, base
|
|
5
|
+
* included. An entry's key is what the router stamps on each history entry it
|
|
6
|
+
* writes, so Back and Forward land on an entry it knows. With no Adapter in
|
|
7
|
+
* the Layer, the app routes in memory and never touches a URL.
|
|
8
|
+
*/
|
|
9
|
+
export interface AdapterShape {
|
|
10
|
+
/** The URL shown now: path and query. */
|
|
11
|
+
readonly url: () => string;
|
|
12
|
+
/** The element on the page the URL names now, after its `#`, decoded; none if it names none. */
|
|
13
|
+
readonly hash?: () => string;
|
|
14
|
+
/** The key the router stamped on the entry shown now, if it wrote it. */
|
|
15
|
+
readonly key: () => string | null;
|
|
16
|
+
/** Hear every change of the URL, whoever made it; gives back how to stop. */
|
|
17
|
+
readonly listen: (changed: () => void) => () => void;
|
|
18
|
+
readonly push: (url: string, key: string) => void;
|
|
19
|
+
readonly replace: (url: string, key: string) => void;
|
|
20
|
+
/** Move through the history by `by` entries: -1 is Back. */
|
|
21
|
+
readonly go: (by: number) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Go to a URL outside the app's base, or another site: hand it to whoever
|
|
24
|
+
* owns it. `replace` takes the place of the entry shown, as a Redirect does.
|
|
25
|
+
*/
|
|
26
|
+
readonly leave: (url: string, options?: {
|
|
27
|
+
readonly replace?: boolean;
|
|
28
|
+
}) => void;
|
|
29
|
+
}
|
|
30
|
+
declare const Adapter_base: Context.ServiceClass<Adapter, "@kestack/kabuki/routing/Adapter", AdapterShape>;
|
|
31
|
+
export declare class Adapter extends Adapter_base {
|
|
32
|
+
}
|
|
33
|
+
/** The key the router keeps in a history entry's state. */
|
|
34
|
+
export declare const KEY = "@kestack/kabuki/routing";
|
|
35
|
+
/** The key in a history entry's state, if the router wrote one. */
|
|
36
|
+
export declare const keyIn: (state: unknown) => string | null;
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/routing/adapter/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AASjC;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,gGAAgG;IAChG,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,MAAM,CAAC;IAC7B,yEAAyE;IACzE,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAClC,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IACrD,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,CACd,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,KACrC,IAAI,CAAC;CACX;;AAED,qBAAa,OAAQ,SAAQ,YAE5B;CAAG;AAEJ,2DAA2D;AAC3D,eAAO,MAAM,GAAG,4BAA4B,CAAC;AAE7C,mEAAmE;AACnE,eAAO,MAAM,KAAK,UAAW,OAAO,KAAG,MAAM,GAAG,IAG/C,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Context } from 'effect';
|
|
2
|
+
export class Adapter extends Context.Service()('@kestack/kabuki/routing/Adapter') {
|
|
3
|
+
}
|
|
4
|
+
/** The key the router keeps in a history entry's state. */
|
|
5
|
+
export const KEY = '@kestack/kabuki/routing';
|
|
6
|
+
/** The key in a history entry's state, if the router wrote one. */
|
|
7
|
+
export const keyIn = (state) => {
|
|
8
|
+
const found = state?.[KEY];
|
|
9
|
+
return typeof found === 'string' ? found : null;
|
|
10
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Layer } from 'effect';
|
|
2
|
+
import { Adapter } from '../index.ts';
|
|
3
|
+
/**
|
|
4
|
+
* The browser's own history, for an app that owns the address: the whole
|
|
5
|
+
* site, or everything under its base.
|
|
6
|
+
*/
|
|
7
|
+
export declare const browser: () => Layer.Layer<Adapter, never, never>;
|
|
8
|
+
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../../src/routing/adapter/browser/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,OAAO,EAAc,MAAM,aAAa,CAAC;AAIlD;;;GAGG;AACH,eAAO,MAAM,OAAO,0CA2BhB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Layer } from 'effect';
|
|
2
|
+
import { Adapter, KEY, keyIn } from '../index.js';
|
|
3
|
+
const CHANGED = '@kestack/kabuki/routing:changed';
|
|
4
|
+
/**
|
|
5
|
+
* The browser's own history, for an app that owns the address: the whole
|
|
6
|
+
* site, or everything under its base.
|
|
7
|
+
*/
|
|
8
|
+
export const browser = () => Layer.sync(Adapter, () => {
|
|
9
|
+
const tell = () => dispatchEvent(new Event(CHANGED));
|
|
10
|
+
return {
|
|
11
|
+
url: () => `${location.pathname}${location.search}`,
|
|
12
|
+
hash: () => decodeURIComponent(location.hash.slice(1)),
|
|
13
|
+
key: () => keyIn(history.state),
|
|
14
|
+
listen: (changed) => {
|
|
15
|
+
addEventListener('popstate', changed);
|
|
16
|
+
addEventListener(CHANGED, changed);
|
|
17
|
+
return () => {
|
|
18
|
+
removeEventListener('popstate', changed);
|
|
19
|
+
removeEventListener(CHANGED, changed);
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
push: (url, key) => {
|
|
23
|
+
history.pushState({ [KEY]: key }, '', url);
|
|
24
|
+
tell();
|
|
25
|
+
},
|
|
26
|
+
replace: (url, key) => {
|
|
27
|
+
history.replaceState({ [KEY]: key }, '', url);
|
|
28
|
+
tell();
|
|
29
|
+
},
|
|
30
|
+
go: (by) => history.go(by),
|
|
31
|
+
leave: (url, options) => options?.replace ? location.replace(url) : location.assign(url),
|
|
32
|
+
};
|
|
33
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/routing/adapter/browser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { browser } from './browser.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/routing/adapter/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACnD,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Adapter, KEY, keyIn } from './adapter.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/routing/adapter/tanstack/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { tanstack } from './tanstack.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Layer } from 'effect';
|
|
2
|
+
import { Adapter } from '../index.ts';
|
|
3
|
+
/** The part of a TanStack Router history the Adapter needs: `router.history`. */
|
|
4
|
+
export interface TanStackHistory {
|
|
5
|
+
readonly location: {
|
|
6
|
+
readonly pathname: string;
|
|
7
|
+
readonly search: string;
|
|
8
|
+
readonly hash?: string;
|
|
9
|
+
readonly state: unknown;
|
|
10
|
+
};
|
|
11
|
+
readonly subscribe: (listener: () => void) => () => void;
|
|
12
|
+
readonly push: (path: string, state?: any) => void;
|
|
13
|
+
readonly replace: (path: string, state?: any) => void;
|
|
14
|
+
readonly go: (index: number) => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* TanStack Router's history, for an app on a TanStack Start catch-all route:
|
|
18
|
+
* TanStack hands everything under the app's base to it, and gets back every
|
|
19
|
+
* URL outside it. Every move goes through TanStack's history, so TanStack
|
|
20
|
+
* keeps its own count of where Back goes and matches the same route again
|
|
21
|
+
* without remounting. By default it finds the router TanStack keeps on the
|
|
22
|
+
* page; `history` hands one over instead.
|
|
23
|
+
*/
|
|
24
|
+
export declare const tanstack: (options?: {
|
|
25
|
+
readonly history?: () => TanStackHistory;
|
|
26
|
+
}) => Layer.Layer<Adapter, never, never>;
|
|
27
|
+
//# sourceMappingURL=tanstack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tanstack.d.ts","sourceRoot":"","sources":["../../../../src/routing/adapter/tanstack/tanstack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,OAAO,EAAc,MAAM,aAAa,CAAC;AAElD,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,QAAQ,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IACzD,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IACnD,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IACtD,QAAQ,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAcD;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,aAAc;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,eAAe,CAAC;CAC1C,uCAsBG,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Layer } from 'effect';
|
|
2
|
+
import { Adapter, KEY, keyIn } from '../index.js';
|
|
3
|
+
/** The router TanStack keeps on `window` in a browser. */
|
|
4
|
+
const fromWindow = () => {
|
|
5
|
+
const router = globalThis.__TSR_ROUTER__;
|
|
6
|
+
if (!router)
|
|
7
|
+
throw new Error('[@kestack/kabuki/routing] no TanStack Router on this page: pass `history` to tanstack()');
|
|
8
|
+
return router.history;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* TanStack Router's history, for an app on a TanStack Start catch-all route:
|
|
12
|
+
* TanStack hands everything under the app's base to it, and gets back every
|
|
13
|
+
* URL outside it. Every move goes through TanStack's history, so TanStack
|
|
14
|
+
* keeps its own count of where Back goes and matches the same route again
|
|
15
|
+
* without remounting. By default it finds the router TanStack keeps on the
|
|
16
|
+
* page; `history` hands one over instead.
|
|
17
|
+
*/
|
|
18
|
+
export const tanstack = (options) => Layer.sync(Adapter, () => {
|
|
19
|
+
const history = (options?.history ?? fromWindow)();
|
|
20
|
+
return {
|
|
21
|
+
url: () => `${history.location.pathname}${history.location.search}`,
|
|
22
|
+
hash: () => decodeURIComponent((history.location.hash ?? '').replace(/^#/, '')),
|
|
23
|
+
key: () => keyIn(history.location.state),
|
|
24
|
+
listen: (changed) => history.subscribe(changed),
|
|
25
|
+
push: (url, key) => history.push(url, { [KEY]: key }),
|
|
26
|
+
replace: (url, key) => history.replace(url, { [KEY]: key }),
|
|
27
|
+
go: (by) => history.go(by),
|
|
28
|
+
// TanStack's history holds this site's URLs alone: another site is
|
|
29
|
+
// the browser's to go to.
|
|
30
|
+
leave: (url, options) => {
|
|
31
|
+
if (new URL(url, location.href).origin !== location.origin)
|
|
32
|
+
return options?.replace
|
|
33
|
+
? location.replace(url)
|
|
34
|
+
: location.assign(url);
|
|
35
|
+
return options?.replace ? history.replace(url) : history.push(url);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Layer } from 'effect';
|
|
2
|
+
import { Loading } from './loading.ts';
|
|
3
|
+
import type { RoutesWithPlaces } from './loading.ts';
|
|
4
|
+
/**
|
|
5
|
+
* A `Loading` that keeps what `inner` loads, for the places of `routes`:
|
|
6
|
+
* `Router.cached(Routes, loading(Routes))`. `size` copies at most: the
|
|
7
|
+
* router's `cacheSize` unless said, 50 unless it says.
|
|
8
|
+
*/
|
|
9
|
+
export declare const cached: <E, R>(routes: RoutesWithPlaces, inner: Layer.Layer<Loading, E, R>, options?: {
|
|
10
|
+
readonly size?: number;
|
|
11
|
+
}) => Layer.Layer<Loading, E, R>;
|
|
12
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/routing/capability/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,EAAU,MAAM,QAAQ,CAAC;AAEvE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,EAAU,gBAAgB,EAAU,MAAM,cAAc,CAAC;AAmDrE;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,EAAE,CAAC,UACjB,gBAAgB,SACjB,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,YACvB;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KACnC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAoLE,CAAC"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { Cause, Clock, Deferred, Effect, Layer, Stream } from 'effect';
|
|
2
|
+
import { Loading } from './loading.js';
|
|
3
|
+
/*
|
|
4
|
+
* The Data cache: the last data each place loaded, one copy per name and
|
|
5
|
+
* what its Loader is handed, newest wins. It sits in front of another
|
|
6
|
+
* `Loading` and lives as long as the Layer, so in a browser, as long as the
|
|
7
|
+
* tab; a server has none.
|
|
8
|
+
*
|
|
9
|
+
* 1. Keep every place loaded, up to `size` copies, each with when it
|
|
10
|
+
* arrived; the least recently used goes first, never one for its
|
|
11
|
+
* age. A failure is never kept.
|
|
12
|
+
* 2. Answer from a copy unless asked `again` or `fresh`: `stale` once it
|
|
13
|
+
* is older than its place's Stale time; a static place's never
|
|
14
|
+
* is.
|
|
15
|
+
* 3. Join a place already being loaded: a click during a preload waits
|
|
16
|
+
* for the same answer rather than asking again, and so does the
|
|
17
|
+
* load behind a stale copy. A fresh ask, a reload's, joins
|
|
18
|
+
* nothing, and an answer is kept only if nothing asked later is,
|
|
19
|
+
* and the copies were not dropped since.
|
|
20
|
+
* 4. Batch the places asked for in the same moment into one ask of the
|
|
21
|
+
* Loading behind, so many places loading at once are one
|
|
22
|
+
* request, each answered as it comes. The load runs on its own:
|
|
23
|
+
* a place that stops waiting does not stop it, and its answer is
|
|
24
|
+
* still kept.
|
|
25
|
+
* 5. Seed with the data a server drew the page with, so a page first
|
|
26
|
+
* seen from the server is kept as one loaded in the browser is,
|
|
27
|
+
* arrived when the server loaded it.
|
|
28
|
+
*
|
|
29
|
+
* A preload loads only the places with no fresh copy.
|
|
30
|
+
*/
|
|
31
|
+
/** Asks of one batch, split so no ask names a place twice: answers come back by name. */
|
|
32
|
+
const rounds = (asks) => {
|
|
33
|
+
const out = [];
|
|
34
|
+
for (const ask of asks) {
|
|
35
|
+
const round = out.find((each) => each.every((other) => other.place.name !== ask.place.name));
|
|
36
|
+
if (round)
|
|
37
|
+
round.push(ask);
|
|
38
|
+
else
|
|
39
|
+
out.push([ask]);
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
};
|
|
43
|
+
const reasonOf = (cause) => {
|
|
44
|
+
const error = Cause.squash(cause);
|
|
45
|
+
return error instanceof Error ? error.message : String(error);
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* A `Loading` that keeps what `inner` loads, for the places of `routes`:
|
|
49
|
+
* `Router.cached(Routes, loading(Routes))`. `size` copies at most: the
|
|
50
|
+
* router's `cacheSize` unless said, 50 unless it says.
|
|
51
|
+
*/
|
|
52
|
+
export const cached = (routes, inner, options) => Layer.effect(Loading, Effect.gen(function* () {
|
|
53
|
+
const source = yield* Loading;
|
|
54
|
+
const size = options?.size ?? routes.options?.cacheSize ?? 50;
|
|
55
|
+
const staleTimes = new Map(routes.every.map((place) => [place.name, place.staleTime]));
|
|
56
|
+
// 1. Keep
|
|
57
|
+
const copies = new Map();
|
|
58
|
+
/** Whether a copy of the place is younger than its Stale time. */
|
|
59
|
+
const freshOf = (place, copy, now) => now - copy.at < (staleTimes.get(place.name) ?? 0);
|
|
60
|
+
const flying = new Map();
|
|
61
|
+
let batch = [];
|
|
62
|
+
let asked = 0;
|
|
63
|
+
/** The last ask before the copies were dropped: no answer to one asked by then is kept. */
|
|
64
|
+
let forgotten = 0;
|
|
65
|
+
const keyOf = (place) => JSON.stringify([place.name, place.params]);
|
|
66
|
+
const keep = (key, copy) => {
|
|
67
|
+
copies.delete(key);
|
|
68
|
+
copies.set(key, copy);
|
|
69
|
+
for (const oldest of copies.keys()) {
|
|
70
|
+
if (copies.size <= size)
|
|
71
|
+
break;
|
|
72
|
+
copies.delete(oldest);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
// 4. Batch
|
|
76
|
+
/** Settles one ask with what its place came to, and keeps its data, unless a newer ask's is kept or the copies were dropped since. */
|
|
77
|
+
const settle = (ask, outcome) => Effect.flatMap(Clock.currentTimeMillis, (at) => {
|
|
78
|
+
const { key, answer, seq, since } = ask;
|
|
79
|
+
if (flying.get(key) === ask)
|
|
80
|
+
flying.delete(key);
|
|
81
|
+
if (outcome._tag === 'Loaded' &&
|
|
82
|
+
seq > forgotten &&
|
|
83
|
+
seq > (copies.get(key)?.seq ?? 0))
|
|
84
|
+
keep(key, { data: outcome.data, seq, at });
|
|
85
|
+
return Deferred.succeed(answer, { ...outcome, at, took: at - since });
|
|
86
|
+
});
|
|
87
|
+
/** Asks the Loading behind for a batch, each settled as it comes if it can say so, and any left unanswered broken. */
|
|
88
|
+
const ask = (asks) => {
|
|
89
|
+
const places = asks.map(({ place }) => place);
|
|
90
|
+
const byName = new Map(asks.map((each) => [each.place.name, each]));
|
|
91
|
+
const answered = source.each
|
|
92
|
+
? Stream.runForEach(source.each(places), ([name, outcome]) => {
|
|
93
|
+
const found = byName.get(name);
|
|
94
|
+
if (!found)
|
|
95
|
+
return Effect.void;
|
|
96
|
+
byName.delete(name);
|
|
97
|
+
return settle(found, outcome);
|
|
98
|
+
})
|
|
99
|
+
: Effect.flatMap(source.load(places), (answers) => Effect.forEach(asks, (each) => {
|
|
100
|
+
const outcome = answers[each.place.name];
|
|
101
|
+
if (!outcome)
|
|
102
|
+
return Effect.void;
|
|
103
|
+
byName.delete(each.place.name);
|
|
104
|
+
return settle(each, outcome);
|
|
105
|
+
}));
|
|
106
|
+
const left = (reason) => Effect.forEach([...byName.values()], (each) => settle(each, { _tag: 'Broken', reason: reason(each.place.name) }));
|
|
107
|
+
return answered.pipe(Effect.andThen(left((name) => `no answer for ${name}`)), Effect.catchCause((cause) => left(() => reasonOf(cause))));
|
|
108
|
+
};
|
|
109
|
+
const flush = Effect.sleep(0).pipe(Effect.andThen(Effect.suspend(() => {
|
|
110
|
+
const taken = batch;
|
|
111
|
+
batch = [];
|
|
112
|
+
return Effect.forEach(rounds(taken), ask, {
|
|
113
|
+
concurrency: 'unbounded',
|
|
114
|
+
});
|
|
115
|
+
})));
|
|
116
|
+
/**
|
|
117
|
+
* The place's answer, being loaded: joined if it already is, else
|
|
118
|
+
* added to the batch. A fresh ask joins nothing: what is on its way
|
|
119
|
+
* was asked before it.
|
|
120
|
+
*/
|
|
121
|
+
const loading = (place, fresh = false) => Effect.flatMap(Clock.currentTimeMillis, (since) => {
|
|
122
|
+
const key = keyOf(place);
|
|
123
|
+
// 3. Join
|
|
124
|
+
const going = flying.get(key);
|
|
125
|
+
if (going && !fresh)
|
|
126
|
+
return Effect.succeed(going.answer);
|
|
127
|
+
const answer = Deferred.makeUnsafe();
|
|
128
|
+
const ask = { key, place, answer, seq: (asked += 1), since };
|
|
129
|
+
flying.set(key, ask);
|
|
130
|
+
batch.push(ask);
|
|
131
|
+
return batch.length === 1
|
|
132
|
+
? Effect.as(Effect.forkDetach(flush), answer)
|
|
133
|
+
: Effect.succeed(answer);
|
|
134
|
+
});
|
|
135
|
+
// 2. Answer
|
|
136
|
+
const one = (place, fresh, again) => Effect.flatMap(Clock.currentTimeMillis, (now) => {
|
|
137
|
+
const key = keyOf(place);
|
|
138
|
+
const copy = copies.get(key);
|
|
139
|
+
if (!fresh && !again && copy) {
|
|
140
|
+
keep(key, copy);
|
|
141
|
+
return Effect.succeed({
|
|
142
|
+
_tag: 'Loaded',
|
|
143
|
+
data: copy.data,
|
|
144
|
+
stale: !freshOf(place, copy, now),
|
|
145
|
+
at: copy.at,
|
|
146
|
+
cached: true,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return Effect.flatMap(loading(place, fresh), Deferred.await);
|
|
150
|
+
});
|
|
151
|
+
return {
|
|
152
|
+
load: (places, options) => Effect.forEach(places, (place) => Effect.map(one(place, options?.fresh === true, options?.again === true), (answer) => [place.name, answer]), { concurrency: 'unbounded' }).pipe(Effect.map((answers) => Object.fromEntries(answers))),
|
|
153
|
+
preload: (places) => Effect.flatMap(Clock.currentTimeMillis, (now) => Effect.forEach(places.filter((place) => {
|
|
154
|
+
const copy = copies.get(keyOf(place));
|
|
155
|
+
return !copy || !freshOf(place, copy, now);
|
|
156
|
+
}), (place) => loading(place), { discard: true })),
|
|
157
|
+
// 5. Seed
|
|
158
|
+
keep: (place, data, arrived) => Effect.flatMap(Clock.currentTimeMillis, (now) => Effect.sync(() => {
|
|
159
|
+
const key = keyOf(place);
|
|
160
|
+
if (!copies.has(key))
|
|
161
|
+
keep(key, { data, seq: (asked += 1), at: arrived ?? now });
|
|
162
|
+
})),
|
|
163
|
+
// What is on its way was asked before: it is neither joined nor kept.
|
|
164
|
+
forget: Effect.sync(() => {
|
|
165
|
+
copies.clear();
|
|
166
|
+
flying.clear();
|
|
167
|
+
forgotten = asked;
|
|
168
|
+
}),
|
|
169
|
+
};
|
|
170
|
+
})).pipe(Layer.provide(inner));
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { Loading, memory, outcomeOf } from './loading.ts';
|
|
2
|
+
export type { Answer, RoutesWithPlaces, Wanted } from './loading.ts';
|
|
3
|
+
export { cached } from './cache.ts';
|
|
4
|
+
export { Navigation, back, backTo, forward, moveOptions, preload, push, reload, replace, } from './navigation.ts';
|
|
5
|
+
export type { AnyRoute, HashScrollOptions, MoveOptions } from './navigation.ts';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/routing/capability/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC1D,YAAY,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EACL,UAAU,EACV,IAAI,EACJ,MAAM,EACN,OAAO,EACP,WAAW,EACX,OAAO,EACP,IAAI,EACJ,MAAM,EACN,OAAO,GACR,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,QAAQ,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC"}
|