@typed/svelte 1.0.0-beta.10
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/LICENSE +21 -0
- package/README.md +140 -0
- package/dist/AsyncData.d.ts +23 -0
- package/dist/AsyncData.d.ts.map +1 -0
- package/dist/AsyncData.js +28 -0
- package/dist/Attachment.d.ts +16 -0
- package/dist/Attachment.d.ts.map +1 -0
- package/dist/Attachment.js +56 -0
- package/dist/Reactive.d.ts +54 -0
- package/dist/Reactive.d.ts.map +1 -0
- package/dist/Reactive.js +180 -0
- package/dist/Router.d.ts +100 -0
- package/dist/Router.d.ts.map +1 -0
- package/dist/Router.js +89 -0
- package/dist/Runtime.d.ts +22 -0
- package/dist/Runtime.d.ts.map +1 -0
- package/dist/Runtime.js +53 -0
- package/dist/Source.d.ts +6 -0
- package/dist/Source.d.ts.map +1 -0
- package/dist/Source.js +12 -0
- package/dist/Store.d.ts +26 -0
- package/dist/Store.d.ts.map +1 -0
- package/dist/Store.js +58 -0
- package/dist/Typed.client.js +77 -0
- package/dist/Typed.d.ts +13 -0
- package/dist/Typed.d.ts.map +1 -0
- package/dist/Typed.js +1 -0
- package/dist/Typed.server.js +70 -0
- package/dist/TypedComponent.d.ts +7 -0
- package/dist/TypedComponent.d.ts.map +1 -0
- package/dist/TypedComponent.js +1 -0
- package/dist/ViewOptions.d.ts +21 -0
- package/dist/ViewOptions.d.ts.map +1 -0
- package/dist/ViewOptions.js +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/internal/Bridge.client.js +17 -0
- package/dist/internal/Bridge.server.js +18 -0
- package/dist/internal/Dom.d.ts +7 -0
- package/dist/internal/Dom.d.ts.map +1 -0
- package/dist/internal/Dom.js +44 -0
- package/dist/internal/Html.d.ts +6 -0
- package/dist/internal/Html.d.ts.map +1 -0
- package/dist/internal/Html.js +25 -0
- package/dist/internal/RenderSnapshot.client.d.ts +3 -0
- package/dist/internal/RenderSnapshot.client.d.ts.map +1 -0
- package/dist/internal/RenderSnapshot.client.js +4 -0
- package/dist/internal/RenderSnapshot.server.d.ts +7 -0
- package/dist/internal/RenderSnapshot.server.d.ts.map +1 -0
- package/dist/internal/RenderSnapshot.server.js +8 -0
- package/dist/view.d.ts +12 -0
- package/dist/view.d.ts.map +1 -0
- package/dist/view.js +59 -0
- package/package.json +100 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-present The Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# @typed/svelte
|
|
2
|
+
|
|
3
|
+
Svelte 5 components inside Typed templates, and Typed renderables inside Svelte. Both directions create their own `display: contents` hosts, preserve SSR node identity during hydration, and release subscriptions with their rendering scope. The caller keeps ownership of its runtime.
|
|
4
|
+
|
|
5
|
+
`view` requires `RandomValues` from `@typed/id/RandomValues` for automatic IDs. This service stays in the returned `Fx` requirements. Provide `RandomValues.Default` (or your own implementation) alongside the renderer at the application boundary; the integration does not choose an entropy source.
|
|
6
|
+
|
|
7
|
+
## Svelte in Typed
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { RandomValues } from "@typed/id/RandomValues";
|
|
11
|
+
import { Fx, RefSubject } from "@typed/fx";
|
|
12
|
+
import { html } from "@typed/template/RenderTemplate";
|
|
13
|
+
import { DomRenderTemplate, render } from "@typed/template/Render";
|
|
14
|
+
import { view } from "@typed/svelte";
|
|
15
|
+
import { Layer } from "effect";
|
|
16
|
+
import Counter from "./Counter.svelte";
|
|
17
|
+
|
|
18
|
+
const application = Fx.gen(function* () {
|
|
19
|
+
const props = yield* RefSubject.make({ label: "Count" });
|
|
20
|
+
const content = html`<main>${view(Counter, props)}</main>`;
|
|
21
|
+
|
|
22
|
+
return render(content, document.body);
|
|
23
|
+
}).pipe(Fx.drainLayer, Layer.provide(Layer.merge(DomRenderTemplate, RandomValues.Default)));
|
|
24
|
+
|
|
25
|
+
const program = Layer.launch(application);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`view(component, props, options?)` accepts a props object, `Effect`, `Stream`, or `Fx` (including `RefSubject`). Component props determine the required shape; the source's errors and required services remain in the returned `Fx`. It mounts once and updates a Svelte store, preserving component state. Once the first render event is published, the ambient Scope keeps the component live even if a consumer takes only that event. Cancellation before the first output releases pending acquisition. Nested published islands remain owned by their rendering Scope even when a sibling prevents the parent from publishing. A finite props source leaves the component mounted until cleanup. `context`, `intro`, `outro`, `recover`, `idPrefix`, `csp`, and `transformError` forward to the corresponding Svelte API.
|
|
29
|
+
|
|
30
|
+
Typed supplies the host through a native element ref. Svelte mounts at that ref and schedules its own commit. For a nested island, refs can run before Typed places the surrounding parent; `onMount` is not a guarantee that the parent is attached. The first Typed output describes the host, and subsequent props update the same component.
|
|
31
|
+
|
|
32
|
+
`view` selects its backend from the native `DomRenderEvent` or `HtmlRenderEvent` produced by its host template. Supply Typed's ordinary `DomRenderTemplate`, `HtmlRenderTemplate`, or `StaticHtmlRenderTemplate` and Scope at the application render boundary. No Svelte renderer layer or service is required. Both backends expose the current Effect context to the Svelte component, so native context and reactive helpers can read application services, navigation, and the current route without creating another runtime. Explicit Svelte `context` entries override inherited entries.
|
|
33
|
+
|
|
34
|
+
## Server rendering and hydration
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import * as Layer from "effect/Layer";
|
|
38
|
+
import { RandomValues } from "@typed/id/RandomValues";
|
|
39
|
+
import { view } from "@typed/svelte";
|
|
40
|
+
import { Fx } from "@typed/fx";
|
|
41
|
+
import { HtmlRenderTemplate, renderToHtml, renderToHtmlString } from "@typed/template/Html";
|
|
42
|
+
import { Effect } from "effect";
|
|
43
|
+
|
|
44
|
+
const page = view(Counter, { label: "Count" });
|
|
45
|
+
const chunks = renderToHtml(page).pipe(
|
|
46
|
+
Fx.provide(Layer.merge(HtmlRenderTemplate, RandomValues.Default)),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Collect a complete string when needed, including static generation.
|
|
50
|
+
const body = await Effect.runPromise(
|
|
51
|
+
renderToHtmlString(page).pipe(
|
|
52
|
+
Effect.provide(Layer.merge(HtmlRenderTemplate, RandomValues.Default)),
|
|
53
|
+
Effect.scoped,
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Prefer `renderToHtml` when the response can consume chunks. The application observes that Fx within its request Scope, supplies the native HTML renderer, and closes the scope when the response completes or is interrupted. Use `StaticHtmlRenderTemplate` for static generation without Typed hydration markers.
|
|
59
|
+
|
|
60
|
+
The surrounding Typed template and Svelte host stream before the component is ready. Svelte's public server renderer returns a complete `body` and `head`, so the component body arrives together after its first props value and asynchronous rendering finish. Application service requirements and source errors stay in the Effect type. `onHead` is optional and receives application `<svelte:head>` output; omit it when the component has no head content.
|
|
61
|
+
|
|
62
|
+
On the client, pass the same Typed template and its server root to `render`. Hydration is automatic. Typed creates an ordinary `div` host and a child range through its native template path. The HTML backend renders Svelte's serialized body into that range; the DOM backend hands its children to Svelte for mounting or hydration. Client and server need matching initial props and component structure. Host identity is generated automatically and restored from server markup during hydration; `options.id` remains an explicit override. Explicit ids must be nonempty and unique across the page; the host id also defaults Svelte's server `idPrefix`.
|
|
63
|
+
|
|
64
|
+
`@typed/svelte` and `@typed/svelte/Typed.svelte` import in Node without `document` or a `.svelte` loader. The package publishes separate compiled client and server components selected through native package conditions. Application Svelte components use their normal Svelte compilation.
|
|
65
|
+
|
|
66
|
+
## Typed in Svelte
|
|
67
|
+
|
|
68
|
+
```svelte
|
|
69
|
+
<script lang="ts">
|
|
70
|
+
import Typed from "@typed/svelte/Typed.svelte";
|
|
71
|
+
import { html } from "@typed/template/RenderTemplate";
|
|
72
|
+
import { Effect } from "effect";
|
|
73
|
+
|
|
74
|
+
let { count } = $props();
|
|
75
|
+
const content = html`<button onclick=${Effect.sync(() => console.log("clicked"))}>
|
|
76
|
+
${count}
|
|
77
|
+
</button>`;
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<Typed value={content} />
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`Typed` accepts any Typed `Renderable` as its `value`, including primitive values, templates, `Effect`, `Stream`, and `Fx`. Its automatically created `div` host has `display: contents`. The optional `id` prop defaults to Svelte's `$props.id()`, stable across SSR and hydration. Views without application services need no runtime. The optional `runtime` prop or nearest `provideRuntime` context supplies application services when needed. Runtime changes or view replacements close the previous rendering scope before starting the next.
|
|
84
|
+
|
|
85
|
+
Use Svelte's own APIs for SSR, static generation, and hydration:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
// Server or static build
|
|
89
|
+
import { render } from "svelte/server";
|
|
90
|
+
const { head, body } = await render(App, { props: { count: 0 } });
|
|
91
|
+
|
|
92
|
+
// Browser, with that HTML already in target
|
|
93
|
+
import { hydrate } from "svelte";
|
|
94
|
+
const app = hydrate(App, { target, props: { count: 0 } });
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The component supplies `HtmlRenderTemplate` and Scope during native Svelte SSR, then supplies `DomRenderTemplate` through its Svelte attachment in the browser. It awaits Typed's HTML rendering. Each response closes its observation scope while leaving a supplied runtime owned by the caller. Svelte retains the server HTML at the opaque boundary, and Typed hydrates its original nodes. Client-only `mount` starts the live view normally. No manually prepared HTML or integration rendering helper is needed. The package compiles its asynchronous component, so consumers do not need to enable Svelte's experimental async compiler option. As with other asynchronous Svelte components, use `await render(...)` on the server.
|
|
98
|
+
|
|
99
|
+
`onReady` fires after the first Typed output attaches; `onError` receives typed failures or renderer defects as an Effect `Cause`. Without `onError`, the host dispatches `typed:error`. Normal interruption is silent. Updating these callbacks or `stopPropagation` preserves the current Typed render; replacing `runtime` or `value` closes and replaces it. Component cleanup never calls `runtime.dispose()`.
|
|
100
|
+
|
|
101
|
+
For an existing element, the lower-level `attachment(runtime, view, options?)` from `@typed/svelte/Attachment` works with Svelte's `{@attach}` syntax. The component is the usual choice when you want automatic hosting and SSR.
|
|
102
|
+
|
|
103
|
+
## Stores, services, and routes
|
|
104
|
+
|
|
105
|
+
- `toReadable(fx, initial)` returns an Effect acquiring a readable Svelte store in the current Scope.
|
|
106
|
+
- `toWritable(ref)` returns an Effect acquiring a writable store backed by a `RefSubject`. Svelte writes publish synchronously and use the captured Effect context. Writes stop after scope closure.
|
|
107
|
+
- `fromReadable(store)` returns an `Fx` that subscribes when observed and unsubscribes on interruption.
|
|
108
|
+
- These store conversions accept non-failing sources because Svelte stores have no error channel. `useSource` and `useAsyncData` expose failures and pending states through Typed's `AsyncData` model.
|
|
109
|
+
- `provideRuntime`, `provideServices`, `useRuntime`, and `useService` use Svelte context and borrowed runtime facades. Reactive runtime stores propagate provider replacements.
|
|
110
|
+
- `useSource`, `useAsyncData`, and `useRefSubject` own work for the Svelte component lifetime. Browser observation starts after mount; server rendering never starts source effects. `useSource` and `useAsyncData` render the provided `initial` snapshot.
|
|
111
|
+
- `useRefSubject(ref, options?)` observes through `useSource` after mount and starts with `undefined` and `NoData`. Values and failures arrive through the normal RefSubject subscription; it does not inspect or execute the ref during component setup. Pass `{ initial: snapshot }` on both server and client when hydration needs an explicit first value; this also narrows the store value from `A | undefined` to `A`. Its `set` and `update` methods accept `A`, return `Promise<Exit>`, and publish after the RefSubject's serialized transaction settles. Read and write failures remain visible in `.state` when native bindings ignore those results. Runtime replacement and unmount interrupt pending writes.
|
|
112
|
+
- `useNavigation`, `useLocation`, `useCurrentPath`, `useCurrentRoute`, `useRoute`, and `provideCurrentRoute` use Typed's actual Navigation, Matcher, decoded params, and structural CurrentRoute services.
|
|
113
|
+
|
|
114
|
+
`useRoute(route, { currentRoute })` accepts a `CurrentRouteTree` or native Svelte readable store. Omission matches relative to the ambient mount and returns `None` outside it. An explicit tree replaces that mount and applies its prefix once; unmatched paths inside it return `None`, while paths outside it retain Typed's `RouteNotFound` failure. Updating the root store restarts observation with the new context. `provideCurrentRoute` continues to set the structural context inherited by descendants.
|
|
115
|
+
|
|
116
|
+
The capabilities are also available through `/Runtime`, `/Reactive`, `/AsyncData`, `/Router`, and `/Store` subpaths. Tests can provide the same Effect service layers, `TestRouter`, or an injected render document used by application code.
|
|
117
|
+
|
|
118
|
+
The hosts are ordinary HTML `div` elements. Place islands in normal HTML content; parser-sensitive table children and SVG children require an appropriate surrounding HTML element. This package targets Svelte 5.57 or newer.
|
|
119
|
+
|
|
120
|
+
## Event propagation at roots
|
|
121
|
+
|
|
122
|
+
Events bubble normally by default. Set `stopPropagation` on `view` options, `<Typed>`, or `attachment` options to select native event names:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
view(Counter, props, { id: "counter", stopPropagation: { click: true, keydown: false } });
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
A `true` entry stops that event when it reaches the automatic host. A `false` entry allows it, overriding an inherited setting. Set the entire option to `false` to disable the inherited policy; omit it to inherit `CurrentRootEvents` from `@typed/template/RootEvents`. The reference defaults to `false` and can be provided with normal Effect service layers, including test layers.
|
|
129
|
+
|
|
130
|
+
The listener runs during bubbling, so component handlers still work. It does not call `preventDefault`, use capture listeners, or stop other handlers on the same root. Ancestor capture listeners have already run; non-bubbling events do not reach this listener. This is a per-event propagation choice, not complete event isolation. Root listeners are scoped to the host and removed on cleanup; `<Typed>` also replaces them when its policy changes.
|
|
131
|
+
|
|
132
|
+
`prefetch(source)` from `/Reactive` samples a value, Effect, Stream, or Fx into an `AsyncData` snapshot in a fresh request scope. Run it through the request runtime, then pass the result as `initial` to `useSource` for matching SSR and hydration. Source failures become failure snapshots; an empty source becomes `NoData`. Use the same helpers with `TestRouter`, injected services, or a test document to exercise server, browser, and test environments.
|
|
133
|
+
|
|
134
|
+
## Checking Svelte files
|
|
135
|
+
|
|
136
|
+
Run `pnpm --filter @typed/svelte check:svelte` to check the package's `.svelte` components, test fixtures, and TypeScript files. `build`, `build:components`, and `test:types` include this check and fail on errors or warnings.
|
|
137
|
+
|
|
138
|
+
Each check recreates the generated `.svelte-check` files so deleted or renamed components cannot leave stale diagnostics.
|
|
139
|
+
|
|
140
|
+
`svelte.config.js` supplies the asynchronous compiler option to the editor, Vite, and the component build. Keep compiler options there so these environments agree. The checker uses TypeScript 7 through `--tsgo`; the TypeScript 6 dependency supplies the JavaScript compiler API that Svelte's tooling also requires.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as AsyncData from "@typed/async-data";
|
|
2
|
+
import type * as Cause from "effect/Cause";
|
|
3
|
+
import * as Option from "effect/Option";
|
|
4
|
+
import { type Readable } from "svelte/store";
|
|
5
|
+
/** Native stores describing one async producer and its last available value. */
|
|
6
|
+
export interface AsyncState<A, E> {
|
|
7
|
+
readonly data: Readable<AsyncData.AsyncData<A, E>>;
|
|
8
|
+
readonly value: Readable<Option.Option<A>>;
|
|
9
|
+
readonly latest: Readable<Option.Option<A>>;
|
|
10
|
+
readonly cause: Readable<Option.Option<Cause.Cause<E>>>;
|
|
11
|
+
readonly error: Readable<Option.Option<E>>;
|
|
12
|
+
readonly pending: Readable<boolean>;
|
|
13
|
+
readonly loading: Readable<boolean>;
|
|
14
|
+
readonly refreshing: Readable<boolean>;
|
|
15
|
+
readonly success: Readable<boolean>;
|
|
16
|
+
readonly failure: Readable<boolean>;
|
|
17
|
+
readonly optimistic: Readable<boolean>;
|
|
18
|
+
readonly refresh: () => void;
|
|
19
|
+
readonly cancel: () => void;
|
|
20
|
+
}
|
|
21
|
+
/** Derives status from Typed AsyncData, including optimistic and refreshing states. */
|
|
22
|
+
export declare function asyncState<A, E>(data: Readable<AsyncData.AsyncData<A, E>>, controls?: Pick<AsyncState<A, E>, "refresh" | "cancel">): AsyncState<A, E>;
|
|
23
|
+
//# sourceMappingURL=AsyncData.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AsyncData.d.ts","sourceRoot":"","sources":["../src/AsyncData.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAW,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAEtD,gFAAgF;AAChF,MAAM,WAAW,UAAU,CAAC,CAAC,EAAE,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAC7B,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EACzC,QAAQ,GAAE,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,GAAG,QAAQ,CAA2C,GAC/F,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAyBlB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as AsyncData from "@typed/async-data";
|
|
2
|
+
import * as Option from "effect/Option";
|
|
3
|
+
import { derived } from "svelte/store";
|
|
4
|
+
/** Derives status from Typed AsyncData, including optimistic and refreshing states. */
|
|
5
|
+
export function asyncState(data, controls = { refresh: () => { }, cancel: () => { } }) {
|
|
6
|
+
let latest = Option.none();
|
|
7
|
+
// Share one projection, so `latest` advances even when only another field is subscribed.
|
|
8
|
+
const snapshot = derived(data, (state) => {
|
|
9
|
+
const value = AsyncData.getSuccess(state);
|
|
10
|
+
if (Option.isSome(value))
|
|
11
|
+
latest = value;
|
|
12
|
+
return { state, value, latest };
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
data: derived(snapshot, (s) => s.state),
|
|
16
|
+
value: derived(snapshot, (s) => s.value),
|
|
17
|
+
latest: derived(snapshot, (s) => s.latest),
|
|
18
|
+
cause: derived(snapshot, (s) => AsyncData.getCause(s.state)),
|
|
19
|
+
error: derived(snapshot, (s) => AsyncData.getError(s.state)),
|
|
20
|
+
pending: derived(snapshot, (s) => AsyncData.isPending(s.state)),
|
|
21
|
+
loading: derived(snapshot, (s) => AsyncData.isLoading(s.state)),
|
|
22
|
+
refreshing: derived(snapshot, (s) => AsyncData.isRefreshing(s.state)),
|
|
23
|
+
success: derived(snapshot, (s) => AsyncData.isSuccess(s.state)),
|
|
24
|
+
failure: derived(snapshot, (s) => AsyncData.isFailure(s.state)),
|
|
25
|
+
optimistic: derived(snapshot, (s) => AsyncData.isOptimistic(s.state)),
|
|
26
|
+
...controls,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as Cause from "effect/Cause";
|
|
2
|
+
import type * as Scope from "effect/Scope";
|
|
3
|
+
import type { Renderable } from "@typed/template/Renderable";
|
|
4
|
+
import type { RenderTemplate } from "@typed/template/RenderTemplate";
|
|
5
|
+
import { type RootEventOptions } from "@typed/template/RootEvents";
|
|
6
|
+
import type { Runtime } from "./Runtime.js";
|
|
7
|
+
/** Options for the render scope owned by a native Svelte attachment. */
|
|
8
|
+
export interface AttachmentOptions<E> {
|
|
9
|
+
readonly stopPropagation?: RootEventOptions;
|
|
10
|
+
/** Runs after Typed attaches its first output; nested frameworks commit on their own schedule. */
|
|
11
|
+
readonly onReady?: () => void;
|
|
12
|
+
readonly onError?: (cause: Cause.Cause<E>) => void;
|
|
13
|
+
}
|
|
14
|
+
/** Supplies the native DOM renderer and a scope while borrowing the runtime. */
|
|
15
|
+
export declare function attachment<const V extends Renderable.Any, ER = never>(runtime: Pick<Runtime<Exclude<Exclude<Renderable.Services<V>, RenderTemplate>, Scope.Scope>, ER>, "runFork">, value: V, options?: AttachmentOptions<Renderable.Error<V> | ER>): (element: HTMLElement) => () => void;
|
|
16
|
+
//# sourceMappingURL=Attachment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Attachment.d.ts","sourceRoot":"","sources":["../src/Attachment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAItC,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAC;AAI3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAc,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,wEAAwE;AACxE,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,QAAQ,CAAC,eAAe,CAAC,EAAE,gBAAgB,CAAC;IAE5C,kGAAkG;IAClG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAE9B,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;CACpD;AAID,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,KAAK,CAAC,CAAC,SAAS,UAAU,CAAC,GAAG,EAAE,EAAE,GAAG,KAAK,EACnE,OAAO,EAAE,IAAI,CACX,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,EAClF,SAAS,CACV,EACD,KAAK,EAAE,CAAC,EACR,OAAO,GAAE,iBAAiB,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,CAAM,GACxD,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,IAAI,CAqBtC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as Cause from "effect/Cause";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Fiber from "effect/Fiber";
|
|
4
|
+
import * as Layer from "effect/Layer";
|
|
5
|
+
import * as Fx from "@typed/fx/Fx";
|
|
6
|
+
import * as Subject from "@typed/fx/Subject";
|
|
7
|
+
import { DomRenderTemplate, render } from "@typed/template/Render";
|
|
8
|
+
import { rootEvents } from "@typed/template/RootEvents";
|
|
9
|
+
const assignments = new WeakMap();
|
|
10
|
+
/** Supplies the native DOM renderer and a scope while borrowing the runtime. */
|
|
11
|
+
export function attachment(runtime, value, options = {}) {
|
|
12
|
+
return (element) => {
|
|
13
|
+
const view = Fx.fromEffect(Effect.acquireUseRelease(Effect.sync(() => runtime.runFork(Layer.launch(renderLayer(value, element, options)))), Fiber.join, Fiber.interrupt)).pipe(Fx.catchCause((cause) => {
|
|
14
|
+
if (!Cause.hasInterruptsOnly(cause)) {
|
|
15
|
+
if (options.onError)
|
|
16
|
+
options.onError(cause);
|
|
17
|
+
else
|
|
18
|
+
element.dispatchEvent(new CustomEvent("typed:error", { detail: cause }));
|
|
19
|
+
}
|
|
20
|
+
return Fx.empty;
|
|
21
|
+
}));
|
|
22
|
+
return (assignments.get(element) ?? mount(element))(view);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function mount(element) {
|
|
26
|
+
const changes = Subject.unsafeMake(1);
|
|
27
|
+
let current;
|
|
28
|
+
Effect.runFork(changes.pipe(Fx.switchMap((view) => Fx.suspend(() => (current === view ? view : Fx.empty))), Fx.ensuring(Effect.sync(() => {
|
|
29
|
+
if (assignments.get(element) === assign)
|
|
30
|
+
assignments.delete(element);
|
|
31
|
+
})), Fx.drain, Effect.scoped));
|
|
32
|
+
function assign(view) {
|
|
33
|
+
current = view;
|
|
34
|
+
Effect.runFork(changes.onSuccess(view));
|
|
35
|
+
return () => {
|
|
36
|
+
if (current !== view)
|
|
37
|
+
return;
|
|
38
|
+
current = undefined;
|
|
39
|
+
Effect.runFork(changes.onSuccess(Fx.empty).pipe(Effect.andThen(() => {
|
|
40
|
+
if (current !== undefined)
|
|
41
|
+
return Effect.void;
|
|
42
|
+
if (assignments.get(element) === assign)
|
|
43
|
+
assignments.delete(element);
|
|
44
|
+
return changes.interrupt;
|
|
45
|
+
})));
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
assignments.set(element, assign);
|
|
49
|
+
return assign;
|
|
50
|
+
}
|
|
51
|
+
const renderLayer = (value, element, options) => Fx.gen(function* () {
|
|
52
|
+
const ready = yield* Effect.cached(Effect.sync(() => options.onReady?.()));
|
|
53
|
+
if (options.stopPropagation !== false)
|
|
54
|
+
yield* rootEvents(element, options.stopPropagation);
|
|
55
|
+
return render(value, element).pipe(Fx.tap(() => ready));
|
|
56
|
+
}).pipe(Fx.provide(DomRenderTemplate.using(element.ownerDocument)), Fx.drain, Layer.effectDiscard);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as AsyncData from "@typed/async-data";
|
|
2
|
+
import * as RefSubject from "@typed/fx/RefSubject";
|
|
3
|
+
import type * as Context from "effect/Context";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Exit from "effect/Exit";
|
|
6
|
+
import * as Scope from "effect/Scope";
|
|
7
|
+
import { type Readable } from "svelte/store";
|
|
8
|
+
import { type AsyncState } from "./AsyncData.js";
|
|
9
|
+
import { type RuntimeSource } from "./Runtime.js";
|
|
10
|
+
import { type Source } from "./Source.js";
|
|
11
|
+
export interface SourceOptions<A, E, R, ER = never> {
|
|
12
|
+
readonly runtime?: RuntimeSource<Exclude<R, Scope.Scope>, ER>;
|
|
13
|
+
/** Used synchronously during SSR and the client's first render. */
|
|
14
|
+
readonly initial?: AsyncData.AsyncData<A, E | ER>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolves a request-local first snapshot and closes its subscription scope.
|
|
18
|
+
* Pass the result as `initial` to both server rendering and client hydration.
|
|
19
|
+
* Empty sources yield NoData; failure causes become AsyncData failures.
|
|
20
|
+
*/
|
|
21
|
+
export declare const prefetch: <A, E = never, R = never>(source: Source<A, E, R>) => Effect.Effect<AsyncData.AsyncData<A, E>, never, Exclude<R, Scope.Scope>>;
|
|
22
|
+
/**
|
|
23
|
+
* Observes an Effect, Stream, Fx, or value after mount. A Svelte source store or
|
|
24
|
+
* runtime store can replace the producer; the previous scope closes first.
|
|
25
|
+
* Server rendering reads only `initial`, and never starts application effects.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useSource<A, E = never, R = never, ER = never>(source: Source<A, E, R> | Readable<Source<A, E, R>>, options?: SourceOptions<A, E, R, ER>): AsyncState<A, E | ER>;
|
|
28
|
+
/** Observes an existing AsyncData producer without nesting its state model. */
|
|
29
|
+
export declare function useAsyncData<A, E, E2 = never, R = never, ER = never>(source: Source<AsyncData.AsyncData<A, E>, E2, R> | Readable<Source<AsyncData.AsyncData<A, E>, E2, R>>, options?: SourceOptions<A, E | E2, R, ER>): AsyncState<A, E | E2 | ER>;
|
|
30
|
+
/** Reads any Effect service using the same runtime, SSR, and failure contract. */
|
|
31
|
+
export declare function useService<I, A, ER = never>(service: Context.Service<I, A>, options?: SourceOptions<A, never, I, ER>): AsyncState<A, ER>;
|
|
32
|
+
/** A native writable whose transactional writes settle with typed Exit results. */
|
|
33
|
+
export interface RefSubjectStore<A, E, Initial = undefined> extends Readable<A | Initial> {
|
|
34
|
+
readonly state: AsyncState<A, E>;
|
|
35
|
+
readonly set: (value: A) => Promise<Exit.Exit<A, E>>;
|
|
36
|
+
readonly update: (f: (value: A) => A) => Promise<Exit.Exit<A, E>>;
|
|
37
|
+
}
|
|
38
|
+
export interface RefSubjectOptions<A, E, R, ER = never> extends Omit<SourceOptions<A, E, R, ER>, "initial"> {
|
|
39
|
+
/** Overrides the first server/client snapshot, including for an uninitialized ref. */
|
|
40
|
+
readonly initial?: A;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Observes any RefSubject, including hydrated refs with typed failures. Writes
|
|
44
|
+
* use its serialized transactions; failures also reach `state` when a native
|
|
45
|
+
* binding ignores the returned Exit. Unmount/runtime replacement cancels writes.
|
|
46
|
+
* The first subscription receives the optional initial value or `undefined`.
|
|
47
|
+
* Lazy initializers only start after mount; `initial` can seed matching SSR and
|
|
48
|
+
* hydration values without executing application effects during rendering.
|
|
49
|
+
*/
|
|
50
|
+
export declare function useRefSubject<A, E = never, R = never, ER = never>(ref: RefSubject.RefSubject<A, E, R>, options: RefSubjectOptions<A, E, R, ER> & {
|
|
51
|
+
readonly initial: A;
|
|
52
|
+
}): RefSubjectStore<A, E | ER, never>;
|
|
53
|
+
export declare function useRefSubject<A, E = never, R = never, ER = never>(ref: RefSubject.RefSubject<A, E, R>, options?: RefSubjectOptions<A, E, R, ER>): RefSubjectStore<A, E | ER>;
|
|
54
|
+
//# sourceMappingURL=Reactive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Reactive.d.ts","sourceRoot":"","sources":["../src/Reactive.ts"],"names":[],"mappings":"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"}
|
package/dist/Reactive.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import * as AsyncData from "@typed/async-data";
|
|
2
|
+
import * as Fx from "@typed/fx/Fx";
|
|
3
|
+
import * as RefSubject from "@typed/fx/RefSubject";
|
|
4
|
+
import * as Cause from "effect/Cause";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as Exit from "effect/Exit";
|
|
7
|
+
import * as Fiber from "effect/Fiber";
|
|
8
|
+
import * as Layer from "effect/Layer";
|
|
9
|
+
import * as Option from "effect/Option";
|
|
10
|
+
import * as Scope from "effect/Scope";
|
|
11
|
+
import { onMount } from "svelte";
|
|
12
|
+
import { derived, get, writable } from "svelte/store";
|
|
13
|
+
import { asyncState } from "./AsyncData.js";
|
|
14
|
+
import { asReadable, useRuntime } from "./Runtime.js";
|
|
15
|
+
import { toFx } from "./Source.js";
|
|
16
|
+
/**
|
|
17
|
+
* Resolves a request-local first snapshot and closes its subscription scope.
|
|
18
|
+
* Pass the result as `initial` to both server rendering and client hydration.
|
|
19
|
+
* Empty sources yield NoData; failure causes become AsyncData failures.
|
|
20
|
+
*/
|
|
21
|
+
export const prefetch = Effect.fn(function (source) {
|
|
22
|
+
return Fx.first(toFx(source)).pipe(Effect.scoped, Effect.matchCause({
|
|
23
|
+
onFailure: AsyncData.failure,
|
|
24
|
+
onSuccess: Option.match({ onNone: () => AsyncData.NoData, onSome: AsyncData.success }),
|
|
25
|
+
}));
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* Observes an Effect, Stream, Fx, or value after mount. A Svelte source store or
|
|
29
|
+
* runtime store can replace the producer; the previous scope closes first.
|
|
30
|
+
* Server rendering reads only `initial`, and never starts application effects.
|
|
31
|
+
*/
|
|
32
|
+
export function useSource(source, options = {}) {
|
|
33
|
+
const runtime = options.runtime
|
|
34
|
+
? asReadable(options.runtime)
|
|
35
|
+
: useRuntime();
|
|
36
|
+
const sources = asReadable(source);
|
|
37
|
+
const data = writable(options.initial ?? AsyncData.NoData);
|
|
38
|
+
let mounted = false;
|
|
39
|
+
let running;
|
|
40
|
+
let refreshCurrent;
|
|
41
|
+
let cancelCurrent;
|
|
42
|
+
const settle = () => data.update(AsyncData.stopLoading);
|
|
43
|
+
const cancel = () => {
|
|
44
|
+
if (mounted)
|
|
45
|
+
cancelCurrent?.();
|
|
46
|
+
else
|
|
47
|
+
running?.interruptUnsafe();
|
|
48
|
+
settle();
|
|
49
|
+
};
|
|
50
|
+
const refresh = () => {
|
|
51
|
+
if (mounted)
|
|
52
|
+
refreshCurrent?.();
|
|
53
|
+
};
|
|
54
|
+
const state = asyncState(data, { refresh, cancel });
|
|
55
|
+
onMount(() => {
|
|
56
|
+
mounted = true;
|
|
57
|
+
const snapshots = derived([runtime, sources], ([currentRuntime, currentSource]) => ({
|
|
58
|
+
runtime: currentRuntime,
|
|
59
|
+
source: currentSource,
|
|
60
|
+
}));
|
|
61
|
+
const changes = Fx.callback((emit) => {
|
|
62
|
+
const stopSnapshots = snapshots.subscribe((snapshot) => emit.succeed({ _tag: "replace", snapshot }));
|
|
63
|
+
refreshCurrent = () => void emit.succeed({ _tag: "refresh" });
|
|
64
|
+
cancelCurrent = () => void emit.succeed({ _tag: "cancel" });
|
|
65
|
+
return Effect.sync(() => {
|
|
66
|
+
stopSnapshots();
|
|
67
|
+
refreshCurrent = undefined;
|
|
68
|
+
cancelCurrent = undefined;
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
const requests = changes.pipe(Fx.skipRepeatsWith((left, right) => left._tag === "replace" &&
|
|
72
|
+
right._tag === "replace" &&
|
|
73
|
+
left.snapshot.runtime === right.snapshot.runtime &&
|
|
74
|
+
left.snapshot.source === right.snapshot.source));
|
|
75
|
+
const lifecycle = Fx.switchMapEffect(requests, (request) => Effect.suspend(() => {
|
|
76
|
+
if (request._tag === "cancel")
|
|
77
|
+
return Effect.void;
|
|
78
|
+
const currentRuntime = get(runtime);
|
|
79
|
+
const currentSource = get(sources);
|
|
80
|
+
if (request._tag === "replace" &&
|
|
81
|
+
(request.snapshot.runtime !== currentRuntime || request.snapshot.source !== currentSource))
|
|
82
|
+
return Effect.void;
|
|
83
|
+
data.update(AsyncData.startLoading);
|
|
84
|
+
return Effect.acquireUseRelease(Effect.sync(() => currentRuntime.runFork(toFx(currentSource).pipe(Fx.map(AsyncData.success), Fx.observe((value) => data.set(value)), Effect.andThen(Effect.sync(settle)), Layer.effectDiscard, Layer.launch))), Fiber.join, Fiber.interrupt).pipe(Effect.catchCause((cause) => Effect.sync(() => {
|
|
85
|
+
if (mounted && !Cause.hasInterruptsOnly(cause))
|
|
86
|
+
data.set(AsyncData.failure(cause));
|
|
87
|
+
})));
|
|
88
|
+
}));
|
|
89
|
+
running = Effect.runFork(Effect.scoped(Fx.drain(lifecycle)));
|
|
90
|
+
return () => {
|
|
91
|
+
mounted = false;
|
|
92
|
+
cancel();
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
return state;
|
|
96
|
+
}
|
|
97
|
+
/** Observes an existing AsyncData producer without nesting its state model. */
|
|
98
|
+
export function useAsyncData(source, options = {}) {
|
|
99
|
+
const outer = useSource(source, {
|
|
100
|
+
runtime: options.runtime,
|
|
101
|
+
initial: AsyncData.success(options.initial ?? AsyncData.NoData),
|
|
102
|
+
});
|
|
103
|
+
const data = derived(outer.data, (state) => AsyncData.flatMap(state, (value, outer) => AsyncData.isPending(outer) ? AsyncData.startLoading(value) : value));
|
|
104
|
+
const state = asyncState(data, { refresh: outer.refresh, cancel: outer.cancel });
|
|
105
|
+
onMount(() => state.data.subscribe(() => { }));
|
|
106
|
+
return state;
|
|
107
|
+
}
|
|
108
|
+
/** Reads any Effect service using the same runtime, SSR, and failure contract. */
|
|
109
|
+
export function useService(service, options = {}) {
|
|
110
|
+
return useSource(Effect.service(service), options);
|
|
111
|
+
}
|
|
112
|
+
export function useRefSubject(ref, options = {}) {
|
|
113
|
+
const runtime = options.runtime
|
|
114
|
+
? asReadable(options.runtime)
|
|
115
|
+
: useRuntime();
|
|
116
|
+
const initial = "initial" in options ? AsyncData.success(options.initial) : AsyncData.NoData;
|
|
117
|
+
const observed = useSource(ref, { runtime, initial });
|
|
118
|
+
const values = writable(Option.getOrUndefined(AsyncData.getSuccess(initial)));
|
|
119
|
+
const result = writable(undefined);
|
|
120
|
+
let writes = Scope.makeUnsafe("parallel");
|
|
121
|
+
let active = false;
|
|
122
|
+
let revision = 0;
|
|
123
|
+
const abort = () => {
|
|
124
|
+
revision++;
|
|
125
|
+
Effect.runFork(Scope.close(writes, Exit.interrupt()));
|
|
126
|
+
if (active)
|
|
127
|
+
writes = Scope.makeUnsafe("parallel");
|
|
128
|
+
};
|
|
129
|
+
const state = asyncState(derived([observed.data, result], ([data, written]) => written ?? data), {
|
|
130
|
+
refresh: () => {
|
|
131
|
+
result.set(undefined);
|
|
132
|
+
observed.refresh();
|
|
133
|
+
},
|
|
134
|
+
cancel: () => {
|
|
135
|
+
abort();
|
|
136
|
+
observed.cancel();
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
onMount(() => {
|
|
140
|
+
active = true;
|
|
141
|
+
const stopRuntime = runtime.subscribe(abort);
|
|
142
|
+
const stopSource = observed.data.subscribe((data) => {
|
|
143
|
+
result.set(undefined);
|
|
144
|
+
const value = AsyncData.getSuccess(data);
|
|
145
|
+
if (Option.isSome(value))
|
|
146
|
+
values.set(value.value);
|
|
147
|
+
});
|
|
148
|
+
const track = state.data.subscribe(() => { });
|
|
149
|
+
return () => {
|
|
150
|
+
active = false;
|
|
151
|
+
abort();
|
|
152
|
+
stopRuntime();
|
|
153
|
+
stopSource();
|
|
154
|
+
track();
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
const run = async (effect) => {
|
|
158
|
+
if (!active)
|
|
159
|
+
return Exit.interrupt();
|
|
160
|
+
const token = ++revision;
|
|
161
|
+
const exit = await get(runtime).runPromiseExit(Effect.scoped(effect), {
|
|
162
|
+
onFiberStart: Fiber.runIn(writes),
|
|
163
|
+
});
|
|
164
|
+
if (active && token === revision) {
|
|
165
|
+
if (Exit.isSuccess(exit)) {
|
|
166
|
+
values.set(exit.value);
|
|
167
|
+
if (AsyncData.isFailure(get(observed.data)))
|
|
168
|
+
observed.refresh();
|
|
169
|
+
}
|
|
170
|
+
result.set(AsyncData.fromExit(exit));
|
|
171
|
+
}
|
|
172
|
+
return exit;
|
|
173
|
+
};
|
|
174
|
+
return {
|
|
175
|
+
subscribe: values.subscribe,
|
|
176
|
+
set: (value) => run(RefSubject.set(ref, value)),
|
|
177
|
+
update: (f) => run(RefSubject.update(ref, f)),
|
|
178
|
+
state,
|
|
179
|
+
};
|
|
180
|
+
}
|
package/dist/Router.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Navigation } from "@typed/navigation/Navigation";
|
|
2
|
+
import type { Destination } from "@typed/navigation/model";
|
|
3
|
+
import { CurrentRoute, type CurrentRouteTree } from "@typed/router/CurrentRoute";
|
|
4
|
+
import * as Matcher from "@typed/router/Matcher";
|
|
5
|
+
import * as Route from "@typed/router/Route";
|
|
6
|
+
import type { Router } from "@typed/router/Router";
|
|
7
|
+
import * as Option from "effect/Option";
|
|
8
|
+
import type * as Scope from "effect/Scope";
|
|
9
|
+
import { type Readable } from "svelte/store";
|
|
10
|
+
import { type SourceOptions } from "./Reactive.js";
|
|
11
|
+
import { type RuntimeSource, type Runtime } from "./Runtime.js";
|
|
12
|
+
/** Follows committed entries from browser, server memory, or test Navigation. */
|
|
13
|
+
export declare function useLocation<ER = never>(options?: SourceOptions<Destination, never, Navigation, ER>): import("./AsyncData.js").AsyncState<{
|
|
14
|
+
readonly url: URL;
|
|
15
|
+
readonly state: unknown;
|
|
16
|
+
readonly sameDocument: boolean;
|
|
17
|
+
readonly id: string;
|
|
18
|
+
readonly key: string;
|
|
19
|
+
}, ER>;
|
|
20
|
+
/** Follows the canonical pathname plus query used by Typed's Matcher. */
|
|
21
|
+
export declare function useCurrentPath<ER = never>(options?: SourceOptions<string, never, Navigation, ER>): import("./AsyncData.js").AsyncState<string, ER>;
|
|
22
|
+
/** Reads the structural route owner; navigation does not rewrite this tree. */
|
|
23
|
+
export declare function useCurrentRoute<ER = never>(options?: SourceOptions<CurrentRouteTree, never, CurrentRoute, ER>): import("./AsyncData.js").AsyncState<CurrentRouteTree, ER>;
|
|
24
|
+
/**
|
|
25
|
+
* Native state plus promise commands using the closest Navigation service.
|
|
26
|
+
* Commands use the latest provider after runtime replacement.
|
|
27
|
+
*/
|
|
28
|
+
export declare function useNavigation<ER = never>(options?: SourceOptions<Destination, never, Navigation, ER>): {
|
|
29
|
+
location: import("./AsyncData.js").AsyncState<{
|
|
30
|
+
readonly url: URL;
|
|
31
|
+
readonly state: unknown;
|
|
32
|
+
readonly sameDocument: boolean;
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly key: string;
|
|
35
|
+
}, ER>;
|
|
36
|
+
entries: import("./AsyncData.js").AsyncState<readonly {
|
|
37
|
+
readonly url: URL;
|
|
38
|
+
readonly state: unknown;
|
|
39
|
+
readonly sameDocument: boolean;
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly key: string;
|
|
42
|
+
}[], ER>;
|
|
43
|
+
transition: import("./AsyncData.js").AsyncState<Option.Option<{
|
|
44
|
+
readonly type: "push" | "reload" | "replace" | "traverse";
|
|
45
|
+
readonly from: {
|
|
46
|
+
readonly url: URL;
|
|
47
|
+
readonly state: unknown;
|
|
48
|
+
readonly sameDocument: boolean;
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly key: string;
|
|
51
|
+
};
|
|
52
|
+
readonly to: {
|
|
53
|
+
readonly url: URL;
|
|
54
|
+
readonly state: unknown;
|
|
55
|
+
readonly sameDocument: boolean;
|
|
56
|
+
readonly key?: string | undefined;
|
|
57
|
+
};
|
|
58
|
+
readonly info?: unknown;
|
|
59
|
+
}>, ER>;
|
|
60
|
+
canGoBack: import("./AsyncData.js").AsyncState<boolean, ER>;
|
|
61
|
+
canGoForward: import("./AsyncData.js").AsyncState<boolean, ER>;
|
|
62
|
+
navigate: (...args: Parameters<typeof Navigation.navigate>) => Promise<{
|
|
63
|
+
readonly url: URL;
|
|
64
|
+
readonly state: unknown;
|
|
65
|
+
readonly sameDocument: boolean;
|
|
66
|
+
readonly id: string;
|
|
67
|
+
readonly key: string;
|
|
68
|
+
}>;
|
|
69
|
+
back: (...args: Parameters<typeof Navigation.back>) => Promise<{
|
|
70
|
+
readonly url: URL;
|
|
71
|
+
readonly state: unknown;
|
|
72
|
+
readonly sameDocument: boolean;
|
|
73
|
+
readonly id: string;
|
|
74
|
+
readonly key: string;
|
|
75
|
+
}>;
|
|
76
|
+
forward: (...args: Parameters<typeof Navigation.forward>) => Promise<{
|
|
77
|
+
readonly url: URL;
|
|
78
|
+
readonly state: unknown;
|
|
79
|
+
readonly sameDocument: boolean;
|
|
80
|
+
readonly id: string;
|
|
81
|
+
readonly key: string;
|
|
82
|
+
}>;
|
|
83
|
+
};
|
|
84
|
+
/** Options for matching one route through Typed's native matcher. */
|
|
85
|
+
export interface RouteOptions<Rt extends Route.Route.Any, ER = never> extends SourceOptions<Option.Option<Route.Route.Type<Rt>>, Matcher.RouteNotFound | Matcher.RouteDecodeError | Matcher.RouteGuardError, Router | Scope.Scope | Route.Route.DecodingServices<Rt>, ER> {
|
|
86
|
+
/** Replaces the ambient mount. A store changes the matching root reactively. */
|
|
87
|
+
readonly currentRoute?: CurrentRouteTree | Readable<CurrentRouteTree>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Decodes reactive route parameters with Typed's matcher. By default, leaving
|
|
91
|
+
* the ambient mount yields None. An explicit root confines the wildcard to that
|
|
92
|
+
* root, so paths outside it retain the native RouteNotFound failure.
|
|
93
|
+
*/
|
|
94
|
+
export declare function useRoute<Rt extends Route.Route.Any, ER = never>(route: Rt, options?: RouteOptions<Rt, ER>): import("./AsyncData.js").AsyncState<Option.Option<Route.Route.Type<Rt>>, ER | Matcher.RouteDecodeError | Matcher.RouteGuardError | Matcher.RouteNotFound>;
|
|
95
|
+
/**
|
|
96
|
+
* Extends the structural CurrentRoute for descendants, including nested Typed
|
|
97
|
+
* content. Pass the full mount route, as with CurrentRoute.extend.
|
|
98
|
+
*/
|
|
99
|
+
export declare function provideCurrentRoute<R = never, ER = never>(route: Route.Route.Any, runtime?: RuntimeSource<R, ER>): Readable<Runtime<R | CurrentRoute, ER>>;
|
|
100
|
+
//# sourceMappingURL=Router.d.ts.map
|