@moku-labs/web 2.0.1 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -0
- package/dist/browser.d.mts +207 -10
- package/dist/browser.mjs +459 -27
- package/dist/index.cjs +459 -26
- package/dist/index.d.cts +207 -10
- package/dist/index.d.mts +207 -10
- package/dist/index.mjs +459 -27
- package/dist/{render-yXHc9BWI.mjs → render-CkdqOmsm.mjs} +7 -1
- package/dist/{render-DHUcHCYs.cjs → render-rf8FAg_Z.cjs} +7 -1
- package/dist/testing.d.mts +36 -1
- package/dist/testing.mjs +18 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,6 +125,51 @@ const app = createApp({
|
|
|
125
125
|
await app.start(); // spa mounts islands onto the SSR'd DOM and intercepts navigation
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
+
### Per-route transitions, scroll, and programmatic nav
|
|
129
|
+
|
|
130
|
+
The *behaviour* of a navigation is declared on the **route** (a typed framework directive, not free-form `.meta()`), with an app-wide default on the `spa` plugin. Both `.transition()` and `.scroll()` override the default for navigations **to** that route:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
spa: { islands, viewTransitions: "crossfade", scrollRestoration: "top" } // app defaults
|
|
134
|
+
|
|
135
|
+
route("/board/{id}").transition("slide") // board↔board slides
|
|
136
|
+
route("/board/{id}/issue/{issueId}")
|
|
137
|
+
.transition("morph") // the card morphs into the panel
|
|
138
|
+
.scroll("preserve") // overlay: don't move the board behind it
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- **`viewTransitions`** accepts `boolean` (`true` → `"crossfade"`, `false` → `"none"`) **or** a named `TransitionMode` (`"none" | "crossfade" | "slide" | "morph"`). A named transition tags the swap so your CSS can target it two ways — `:root[data-view-transition~="slide"]` (every View-Transitions engine) and the standards-track `:active-view-transition-type(slide)` — and a shared `view-transition-name` left across the swap morphs one element into another. Reduced-motion and unsupported browsers fall back to an instant swap.
|
|
142
|
+
- **`scrollRestoration`** is `"top"` (reset, the default) or `"preserve"` (keep the current scroll — e.g. opening an overlay route over content that must stay still). Back/forward always restores its saved position.
|
|
143
|
+
- **`ctx.navigate(path, options?)`** — every island can navigate programmatically with no `app` handle (an always-present context member, like `ctx.set`/`ctx.url`). `app.spa.navigate` takes the same optional `{ scroll }`.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
createIsland("issue", {
|
|
147
|
+
// `null` renders nothing but stays mountable (Preact unmount, not innerHTML="") —
|
|
148
|
+
// the correct "empty" for a persistent overlay panel, so it re-opens cleanly:
|
|
149
|
+
render: (s) => (s.open ? h(IssuePanel, { issue: s.issue }) : null),
|
|
150
|
+
events: { "click [data-close]": (ctx) => ctx.navigate(ctx.url("board", { id: ctx.params.id })) },
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Live realtime channels
|
|
155
|
+
|
|
156
|
+
`createChannel` is a self-healing client WebSocket primitive for islands that consume a server push stream (e.g. a Durable Object). It owns reconnect-with-backoff, an optional keepalive, a pre-seed buffer that closes the connect→load race, refcounted `subscribe` (first in connects, last out disconnects), and an optimistic `deliverLocal` echo — browser-only and tree-shaken away unless used:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { createChannel } from "@moku-labs/web/browser";
|
|
160
|
+
|
|
161
|
+
const board = createChannel<BoardPatch>({
|
|
162
|
+
url: (id) => `${location.origin.replace(/^http/, "ws")}/ws/board/${id}`,
|
|
163
|
+
keepAlive: { send: "ping", ignore: "pong" },
|
|
164
|
+
bufferUntilSeed: true,
|
|
165
|
+
});
|
|
166
|
+
// island onMount:
|
|
167
|
+
const off = board.subscribe(boardId, (patch) => applyPatch(ctx, patch));
|
|
168
|
+
ctx.cleanup(off);
|
|
169
|
+
const snapshot = await getBoard(boardId);
|
|
170
|
+
board.seed(boardId); // flush anything that arrived during the load
|
|
171
|
+
```
|
|
172
|
+
|
|
128
173
|
## How it works
|
|
129
174
|
|
|
130
175
|
### Three layers, one `createApp`
|
package/dist/browser.d.mts
CHANGED
|
@@ -278,7 +278,7 @@ interface PathMatcher {
|
|
|
278
278
|
} | null;
|
|
279
279
|
}
|
|
280
280
|
declare namespace types_d_exports$3 {
|
|
281
|
-
export { Api$2 as Api, ClientRoute, CompileInput, CompiledRoute, Config$2 as Config, ExtractApi$1 as ExtractApi, ExtractRouteParams, ExtractSegmentParameter, GenerateContext, HeadConfig$1 as HeadConfig, LayoutContext, LoadContext, MatcherTable, Prettify, RouteBuilder, RouteContext, RouteDefinition, RouteHandlers, RouteMap, RouteRequire, RouteState, RouterApi, RouterConfig, RouterState, State$2 as State, TypedRoute, Urls };
|
|
281
|
+
export { Api$2 as Api, ClientRoute, CompileInput, CompiledRoute, Config$2 as Config, ExtractApi$1 as ExtractApi, ExtractRouteParams, ExtractSegmentParameter, GenerateContext, HeadConfig$1 as HeadConfig, LayoutContext, LoadContext, MatcherTable, Prettify, RouteBuilder, RouteContext, RouteDefinition, RouteHandlers, RouteMap, RouteRequire, RouteState, RouterApi, RouterConfig, RouterState, ScrollMode, State$2 as State, TransitionMode, TypedRoute, Urls };
|
|
282
282
|
}
|
|
283
283
|
/**
|
|
284
284
|
* Param contribution of a single path segment. `{name:?}` / `:name?` → optional;
|
|
@@ -426,6 +426,27 @@ interface HeadConfig$1 {
|
|
|
426
426
|
/** Arbitrary extra head fields. */
|
|
427
427
|
readonly [key: string]: unknown;
|
|
428
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* Named view-transition behaviour for a navigation TO a route — a closed, typed
|
|
431
|
+
* vocabulary the SPA kernel interprets (NOT free-form app data, so it earns a
|
|
432
|
+
* first-class `.transition()` method instead of a `.meta()` key):
|
|
433
|
+
* - `"none"` — swap with no View Transition (instant);
|
|
434
|
+
* - `"crossfade"` — the default root crossfade (`startViewTransition(swap)`);
|
|
435
|
+
* - `"slide"` / `"morph"` — a named transition; the kernel tags the transition with
|
|
436
|
+
* `types: ["slide"|"morph"]` so consumer CSS can target `:active-view-transition-type(slide)`
|
|
437
|
+
* (and shared `view-transition-name`s morph one element into another).
|
|
438
|
+
*
|
|
439
|
+
* A route's `.transition()` OVERRIDES the app-wide default (`spa.viewTransitions`).
|
|
440
|
+
*/
|
|
441
|
+
type TransitionMode = "none" | "crossfade" | "slide" | "morph";
|
|
442
|
+
/**
|
|
443
|
+
* Scroll behaviour for a navigation TO a route — a closed, typed vocabulary the SPA
|
|
444
|
+
* kernel interprets (a first-class `.scroll()` method, not a `.meta()` key):
|
|
445
|
+
* - `"top"` — reset to the top of the page on the swap (the default for forward navs);
|
|
446
|
+
* - `"preserve"` — keep the current scroll position (e.g. opening an overlay/issue route
|
|
447
|
+
* over a board that must stay still). A route's `.scroll()` OVERRIDES `spa.scrollRestoration`.
|
|
448
|
+
*/
|
|
449
|
+
type ScrollMode = "top" | "preserve";
|
|
429
450
|
/**
|
|
430
451
|
* Fluent route builder. Each chain method returns the same builder with a
|
|
431
452
|
* (possibly widened) state generic. Only `.load()` widens the data type `D`.
|
|
@@ -458,6 +479,24 @@ interface RouteBuilder<S extends RouteState> extends RouteDefinition {
|
|
|
458
479
|
* projected verbatim into `clientManifest()` and shipped to the browser.
|
|
459
480
|
*/
|
|
460
481
|
meta(meta: Record<string, unknown>): RouteBuilder<S>;
|
|
482
|
+
/**
|
|
483
|
+
* Declare the view-transition behaviour for navigations TO this route — a typed
|
|
484
|
+
* framework directive (the SPA kernel reads it), overriding the app-wide
|
|
485
|
+
* `spa.viewTransitions` default. See {@link TransitionMode}.
|
|
486
|
+
*
|
|
487
|
+
* @example
|
|
488
|
+
* route("/board/{id}/issue/{issueId}").transition("morph"); // card → panel
|
|
489
|
+
*/
|
|
490
|
+
transition(mode: TransitionMode): RouteBuilder<S>;
|
|
491
|
+
/**
|
|
492
|
+
* Declare the scroll behaviour for navigations TO this route — a typed framework
|
|
493
|
+
* directive (the SPA kernel reads it), overriding the app-wide
|
|
494
|
+
* `spa.scrollRestoration` default. See {@link ScrollMode}.
|
|
495
|
+
*
|
|
496
|
+
* @example
|
|
497
|
+
* route("/board/{id}/issue/{issueId}").scroll("preserve"); // overlay: don't move the board
|
|
498
|
+
*/
|
|
499
|
+
scroll(mode: ScrollMode): RouteBuilder<S>;
|
|
461
500
|
/** Attach a JSON serializer for the route's data. */
|
|
462
501
|
toJson(handler: (ctx: RouteContext<S>) => unknown): RouteBuilder<S>;
|
|
463
502
|
/** Override the output file-path producer. */
|
|
@@ -490,6 +529,10 @@ interface RouteDefinition {
|
|
|
490
529
|
readonly pattern: string;
|
|
491
530
|
/** Metadata bag accumulated from `.meta()` (named `_meta` to avoid clashing with the `.meta()` builder method). */
|
|
492
531
|
readonly _meta: Record<string, unknown>;
|
|
532
|
+
/** Named view-transition behaviour from `.transition()` (read by the SPA kernel); undefined → app default. */
|
|
533
|
+
readonly _transition?: TransitionMode;
|
|
534
|
+
/** Scroll behaviour from `.scroll()` (read by the SPA kernel); undefined → app default. */
|
|
535
|
+
readonly _scroll?: ScrollMode;
|
|
493
536
|
/** Build-time handler bag (load/render/head/generate/toJson/toFile). */
|
|
494
537
|
readonly _handlers: RouteHandlers;
|
|
495
538
|
}
|
|
@@ -871,7 +914,12 @@ type Api$1 = {
|
|
|
871
914
|
composeTitle(head: HeadConfig | undefined): string;
|
|
872
915
|
};
|
|
873
916
|
declare namespace types_d_exports$4 {
|
|
874
|
-
export { AnyVNode, ExtractApi, ISLAND_HOOK_NAMES, IslandContext, IslandDef, IslandEventHandler, IslandEvents, IslandHooks, IslandInstance, IslandRender, IslandRouteSlice, IslandSpec, IslandSpecExtras, IslandStateFactory, PageData, RenderResult, ResolvedSpaConfig, SpaApi, SpaConfig, SpaContext, SpaDataReader, SpaEmitFunction, SpaEvents, SpaKernel, SpaKernelDeps, SpaRequire, SpaState };
|
|
917
|
+
export { AnyVNode, ExtractApi, ISLAND_HOOK_NAMES, IslandContext, IslandDef, IslandEventHandler, IslandEvents, IslandHooks, IslandInstance, IslandRender, IslandRouteSlice, IslandSpec, IslandSpecExtras, IslandStateFactory, NavigateOptions, PageData, RenderResult, ResolvedSpaConfig, ScrollMode, SpaApi, SpaConfig, SpaContext, SpaDataReader, SpaEmitFunction, SpaEvents, SpaKernel, SpaKernelDeps, SpaRequire, SpaState, TransitionMode };
|
|
918
|
+
}
|
|
919
|
+
/** Per-navigation options for the programmatic navigators (`ctx.navigate` / `app.spa.navigate`). */
|
|
920
|
+
interface NavigateOptions {
|
|
921
|
+
/** Override the scroll behaviour for this single navigation (else the route/app default). */
|
|
922
|
+
scroll?: ScrollMode;
|
|
875
923
|
}
|
|
876
924
|
/** Payload map for the events `spa` emits, used to type the kernel's `emit` closure. */
|
|
877
925
|
type SpaEvents = {
|
|
@@ -946,10 +994,21 @@ type SpaConfig = {
|
|
|
946
994
|
*/
|
|
947
995
|
swapSelector?: string;
|
|
948
996
|
/**
|
|
949
|
-
*
|
|
950
|
-
*
|
|
997
|
+
* The app-wide default View-Transition behaviour for navigations. Accepts a
|
|
998
|
+
* boolean (legacy: `true` → `"crossfade"`, `false` → `"none"`) OR a named
|
|
999
|
+
* {@link TransitionMode} (`"none" | "crossfade" | "slide" | "morph"`). A route's
|
|
1000
|
+
* `.transition()` overrides this per-route. Falls back to an instant swap when the
|
|
1001
|
+
* View Transitions API is unsupported or under `prefers-reduced-motion`. Defaults
|
|
1002
|
+
* to `false` (`"none"`).
|
|
1003
|
+
*/
|
|
1004
|
+
viewTransitions?: boolean | TransitionMode;
|
|
1005
|
+
/**
|
|
1006
|
+
* The app-wide default scroll behaviour for forward navigations: `"top"` (reset to
|
|
1007
|
+
* the top, the default) or `"preserve"` (keep the current scroll). A route's
|
|
1008
|
+
* `.scroll()` overrides this per-route. Traverse (back/forward) always restores its
|
|
1009
|
+
* saved position regardless. Defaults to `"top"`.
|
|
951
1010
|
*/
|
|
952
|
-
|
|
1011
|
+
scrollRestoration?: ScrollMode;
|
|
953
1012
|
/**
|
|
954
1013
|
* Show the in-house top progress bar during navigation. Defaults to `true`.
|
|
955
1014
|
*/
|
|
@@ -964,8 +1023,12 @@ type SpaConfig = {
|
|
|
964
1023
|
interface ResolvedSpaConfig {
|
|
965
1024
|
/** CSS selector for the swapped page region. */
|
|
966
1025
|
swapSelector: string;
|
|
967
|
-
/** Whether View Transitions are enabled. */
|
|
1026
|
+
/** Whether View Transitions are enabled at all (false ⇒ always instant swap). */
|
|
968
1027
|
viewTransitions: boolean;
|
|
1028
|
+
/** The app-wide default transition mode applied when a route declares none. */
|
|
1029
|
+
defaultTransition: TransitionMode;
|
|
1030
|
+
/** The app-wide default scroll behaviour applied when a route declares none. */
|
|
1031
|
+
scrollRestoration: ScrollMode;
|
|
969
1032
|
/** Whether the progress bar is enabled. */
|
|
970
1033
|
progressBar: boolean;
|
|
971
1034
|
/** Pre-registered islands. */
|
|
@@ -976,9 +1039,14 @@ interface ResolvedSpaConfig {
|
|
|
976
1039
|
* - a Preact `VNode` — committed into the host through the lazy Preact gate (`commitVNode`);
|
|
977
1040
|
* - a `Node` — replaces the host's children;
|
|
978
1041
|
* - a `string` — set as the host's `innerHTML`;
|
|
1042
|
+
* - `null` — render NOTHING but stay mountable: the host's Preact tree is unmounted via
|
|
1043
|
+
* `render(null, host)` (NOT `innerHTML = ""`), so a later non-empty render re-commits
|
|
1044
|
+
* cleanly. This is the correct "empty" for a persistent render-island (e.g. a closed
|
|
1045
|
+
* overlay panel) — returning `""` instead corrupts Preact's retained vdom and the panel
|
|
1046
|
+
* never re-opens;
|
|
979
1047
|
* - `void`/`undefined` — the render mutated the DOM itself (DOM-only islands → no Preact loaded).
|
|
980
1048
|
*/
|
|
981
|
-
type RenderResult = AnyVNode | Node | string | void;
|
|
1049
|
+
type RenderResult = AnyVNode | Node | string | null | void;
|
|
982
1050
|
/**
|
|
983
1051
|
* A Preact `VNode` of ANY props shape. A render returns `h(Component, props)`, i.e. a
|
|
984
1052
|
* `VNode<SomeProps>`; the props generic is invariant under `exactOptionalPropertyTypes`,
|
|
@@ -1064,6 +1132,21 @@ interface IslandContext<S = undefined> {
|
|
|
1064
1132
|
readonly locale: string;
|
|
1065
1133
|
/** Build a link to a named route by pattern substitution (same output as `app.router.toUrl`). */
|
|
1066
1134
|
readonly url: (name: string, params?: Record<string, string>) => string;
|
|
1135
|
+
/**
|
|
1136
|
+
* Programmatically navigate the SPA to an internal path (same swap pipeline as a
|
|
1137
|
+
* link click; no-op without a DOM). Build `path` from the route map's `urls`/`ctx.url`,
|
|
1138
|
+
* never a literal. Pass `{ scroll: "preserve" }` to keep the current scroll for this nav.
|
|
1139
|
+
*
|
|
1140
|
+
* An ALWAYS-PRESENT member (like `set`/`flush`/`cleanup`) so every island can navigate
|
|
1141
|
+
* without an `app` handle — no synthesized anchor click.
|
|
1142
|
+
*
|
|
1143
|
+
* @param path - The internal destination path (e.g. `ctx.url("board", { id })`).
|
|
1144
|
+
* @param options - Optional per-navigation overrides (e.g. `{ scroll: "preserve" }`).
|
|
1145
|
+
* @returns void
|
|
1146
|
+
* @example
|
|
1147
|
+
* ctx.navigate(ctx.url("issue", { id, issueId }));
|
|
1148
|
+
*/
|
|
1149
|
+
readonly navigate: (path: string, options?: NavigateOptions) => void;
|
|
1067
1150
|
/** The live per-instance state (the object returned by `spec.state`). `undefined` for legacy hooks-only islands. */
|
|
1068
1151
|
readonly state: S;
|
|
1069
1152
|
/**
|
|
@@ -1306,11 +1389,12 @@ interface SpaKernel {
|
|
|
1306
1389
|
* Process a navigation to `path`: fetch then swap then head-sync then emit.
|
|
1307
1390
|
*
|
|
1308
1391
|
* @param path - The target path to navigate to.
|
|
1392
|
+
* @param options - Optional per-navigation overrides (e.g. `{ scroll: "preserve" }`).
|
|
1309
1393
|
* @returns void
|
|
1310
1394
|
* @example
|
|
1311
1395
|
* kernel.processNav("/about");
|
|
1312
1396
|
*/
|
|
1313
|
-
processNav(path: string): void;
|
|
1397
|
+
processNav(path: string, options?: NavigateOptions): void;
|
|
1314
1398
|
/**
|
|
1315
1399
|
* Query the swap region and mount islands for matching elements.
|
|
1316
1400
|
*
|
|
@@ -1342,6 +1426,12 @@ interface SpaState {
|
|
|
1342
1426
|
destroyRouter: (() => void) | null;
|
|
1343
1427
|
/** Whether the browser runtime has been booted. */
|
|
1344
1428
|
started: boolean;
|
|
1429
|
+
/**
|
|
1430
|
+
* The kernel's programmatic navigator, bound at init (null until then). The island
|
|
1431
|
+
* context's `ctx.navigate` reads through this — the same decoupled seam `islandApis`
|
|
1432
|
+
* uses — so `islands.ts` never imports the kernel.
|
|
1433
|
+
*/
|
|
1434
|
+
navigate: ((path: string, options?: NavigateOptions) => void) | null;
|
|
1345
1435
|
/** The single shared SPA kernel instance (null until onInit builds it). */
|
|
1346
1436
|
kernel: SpaKernel | null;
|
|
1347
1437
|
}
|
|
@@ -1360,11 +1450,12 @@ type SpaApi = {
|
|
|
1360
1450
|
* Programmatically navigate to a path (client runtime; no-op without a DOM).
|
|
1361
1451
|
*
|
|
1362
1452
|
* @param path - Target path (pathname, optionally with search/hash).
|
|
1453
|
+
* @param options - Optional per-navigation overrides (e.g. `{ scroll: "preserve" }`).
|
|
1363
1454
|
* @returns void
|
|
1364
1455
|
* @example
|
|
1365
1456
|
* app.spa.navigate("/about");
|
|
1366
1457
|
*/
|
|
1367
|
-
navigate(path: string): void;
|
|
1458
|
+
navigate(path: string, options?: NavigateOptions): void;
|
|
1368
1459
|
/**
|
|
1369
1460
|
* Read the current resolved URL.
|
|
1370
1461
|
*
|
|
@@ -1600,6 +1691,112 @@ declare const headPlugin: import("@moku-labs/core").PluginInstance<"head", Confi
|
|
|
1600
1691
|
buildArticleHead: typeof buildArticleHead;
|
|
1601
1692
|
};
|
|
1602
1693
|
//#endregion
|
|
1694
|
+
//#region src/plugins/spa/channel.d.ts
|
|
1695
|
+
/**
|
|
1696
|
+
* @file spa plugin — `createChannel`, a client realtime WebSocket primitive.
|
|
1697
|
+
*
|
|
1698
|
+
* A live, self-healing message channel for islands that consume a server push stream
|
|
1699
|
+
* (e.g. a Durable Object broadcasting board patches). It owns the parts every real-time
|
|
1700
|
+
* app otherwise hand-rolls: a single active socket bound to one channel `id`, refcounted
|
|
1701
|
+
* `subscribe` (first subscriber connects, last disconnects — so cross-island mount order
|
|
1702
|
+
* stops being load-bearing), exponential-backoff auto-reconnect, an optional keepalive
|
|
1703
|
+
* ping, a pre-seed buffer (frames that arrive before the island's snapshot loads are
|
|
1704
|
+
* queued and flushed on `seed`, so the connect→load race drops nothing), and an
|
|
1705
|
+
* optimistic `deliverLocal` echo.
|
|
1706
|
+
*
|
|
1707
|
+
* Browser-only (uses `WebSocket`); reached ONLY through an explicit `createChannel(...)`
|
|
1708
|
+
* call, so an app that never opens a channel never pulls this module into its graph.
|
|
1709
|
+
* @see {@link createChannel} for the usage example.
|
|
1710
|
+
*/
|
|
1711
|
+
/** Keepalive configuration: a frame sent on an interval, and the reply to ignore. */
|
|
1712
|
+
interface ChannelKeepAlive {
|
|
1713
|
+
/** The keepalive frame to send (e.g. `"ping"`). */
|
|
1714
|
+
send: string;
|
|
1715
|
+
/** A reply frame to ignore rather than parse (e.g. `"pong"`). */
|
|
1716
|
+
ignore?: string;
|
|
1717
|
+
/** Interval between keepalive sends, in ms. Defaults to `30000`. */
|
|
1718
|
+
everyMs?: number;
|
|
1719
|
+
}
|
|
1720
|
+
/** Auto-reconnect backoff configuration (exponential, capped). */
|
|
1721
|
+
interface ChannelReconnect {
|
|
1722
|
+
/** First retry delay in ms (doubles each attempt). Defaults to `500`. */
|
|
1723
|
+
baseMs?: number;
|
|
1724
|
+
/** Maximum retry delay in ms (the backoff ceiling). Defaults to `8000`. */
|
|
1725
|
+
maxMs?: number;
|
|
1726
|
+
}
|
|
1727
|
+
/** Options for {@link createChannel}. */
|
|
1728
|
+
interface ChannelOptions<T> {
|
|
1729
|
+
/** Build the `ws(s)://` URL for a channel `id`. */
|
|
1730
|
+
url: (id: string) => string;
|
|
1731
|
+
/**
|
|
1732
|
+
* Parse a raw text frame into a message, or return `undefined` to drop it (malformed
|
|
1733
|
+
* frame / keepalive reply). Defaults to `JSON.parse` (dropping frames that fail to parse).
|
|
1734
|
+
*/
|
|
1735
|
+
parse?: (raw: string) => T | undefined;
|
|
1736
|
+
/** Keepalive ping configuration (off when omitted). */
|
|
1737
|
+
keepAlive?: ChannelKeepAlive;
|
|
1738
|
+
/** Auto-reconnect backoff (defaults applied when omitted; pass `false` to disable). */
|
|
1739
|
+
reconnect?: ChannelReconnect | false;
|
|
1740
|
+
/**
|
|
1741
|
+
* Buffer frames that arrive before {@link Channel.seed} and flush them (in arrival
|
|
1742
|
+
* order) on seed, closing the connect→load race. Defaults to `false` (deliver live).
|
|
1743
|
+
*/
|
|
1744
|
+
bufferUntilSeed?: boolean;
|
|
1745
|
+
}
|
|
1746
|
+
/** A live realtime channel (the surface returned by {@link createChannel}). */
|
|
1747
|
+
interface Channel<T> {
|
|
1748
|
+
/**
|
|
1749
|
+
* Subscribe to messages for channel `id`, connecting (or switching) the single
|
|
1750
|
+
* active socket to it. Refcounted: the first subscriber connects, the last to
|
|
1751
|
+
* unsubscribe disconnects. Returns an unsubscribe function.
|
|
1752
|
+
*
|
|
1753
|
+
* @param id - The channel id to subscribe to.
|
|
1754
|
+
* @param handler - Called with each delivered message.
|
|
1755
|
+
* @returns A function that removes this handler (and disconnects when it was the last).
|
|
1756
|
+
*/
|
|
1757
|
+
subscribe(id: string, handler: (message: T) => void): () => void;
|
|
1758
|
+
/**
|
|
1759
|
+
* Mark the channel seeded and flush the pre-seed buffer (no-op unless
|
|
1760
|
+
* `bufferUntilSeed` and `id` is the active channel). Idempotent.
|
|
1761
|
+
*
|
|
1762
|
+
* @param id - The channel id whose buffer to flush (must be the active id).
|
|
1763
|
+
*/
|
|
1764
|
+
seed(id: string): void;
|
|
1765
|
+
/**
|
|
1766
|
+
* Deliver a message to the local handlers immediately (the optimistic-update path —
|
|
1767
|
+
* the acting client reflects its own mutation without waiting for the server echo).
|
|
1768
|
+
*
|
|
1769
|
+
* @param id - The channel id (must be the active id; ignored otherwise).
|
|
1770
|
+
* @param message - The message to deliver locally now.
|
|
1771
|
+
*/
|
|
1772
|
+
deliverLocal(id: string, message: T): void;
|
|
1773
|
+
/**
|
|
1774
|
+
* Send a raw text frame over the live socket (no-op when not open).
|
|
1775
|
+
*
|
|
1776
|
+
* @param raw - The frame to send.
|
|
1777
|
+
*/
|
|
1778
|
+
send(raw: string): void;
|
|
1779
|
+
/**
|
|
1780
|
+
* The currently-subscribed channel id, or `undefined` when disconnected.
|
|
1781
|
+
*
|
|
1782
|
+
* @returns The active id, or undefined.
|
|
1783
|
+
*/
|
|
1784
|
+
current(): string | undefined;
|
|
1785
|
+
/** Close the socket, cancel reconnect, and clear all handlers. */
|
|
1786
|
+
disconnect(): void;
|
|
1787
|
+
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Create a live realtime channel — a single self-healing WebSocket bound to one channel
|
|
1790
|
+
* `id` at a time, with refcounted subscription, auto-reconnect, an optional keepalive, a
|
|
1791
|
+
* pre-seed buffer, and an optimistic local-echo path. See the file header for the model.
|
|
1792
|
+
*
|
|
1793
|
+
* @param options - The channel configuration (see {@link ChannelOptions}).
|
|
1794
|
+
* @returns The {@link Channel} control surface.
|
|
1795
|
+
* @example
|
|
1796
|
+
* const board = createChannel<BoardPatch>({ url: (id) => `wss://host/ws/board/${id}` });
|
|
1797
|
+
*/
|
|
1798
|
+
declare function createChannel<T>(options: ChannelOptions<T>): Channel<T>;
|
|
1799
|
+
//#endregion
|
|
1603
1800
|
//#region src/plugins/spa/islands.d.ts
|
|
1604
1801
|
/**
|
|
1605
1802
|
* Create a validated island definition. Accepts either the legacy hooks-only form
|
|
@@ -2417,4 +2614,4 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
|
|
|
2417
2614
|
*/
|
|
2418
2615
|
declare const createPlugin: import("@moku-labs/core").BoundCreatePluginFunction<Config$5, Events, import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", import("@moku-labs/common").LogConfig, import("@moku-labs/common").LogState, import("@moku-labs/common").LogApi>, import("@moku-labs/core").CorePluginInstance<"env", import("@moku-labs/common").EnvConfig, import("@moku-labs/common").EnvState, import("@moku-labs/common").EnvApi>]>>;
|
|
2419
2616
|
//#endregion
|
|
2420
|
-
export { types_d_exports as Content, types_d_exports$1 as Data, type Env, types_d_exports$2 as Head, type Log, types_d_exports$3 as Router, types_d_exports$4 as Spa, browserEnv, buildArticleHead, canonical, contentPlugin, createApp, createIsland, createPlugin, createUrls, dataPlugin, defineRoutes, envPlugin, feedLink, headPlugin, hreflang, i18nPlugin, jsonLd, lazyEmbed, logPlugin, meta, og, route, routerPlugin, sitePlugin, spaPlugin, twitter };
|
|
2617
|
+
export { type Channel, type ChannelKeepAlive, type ChannelOptions, type ChannelReconnect, types_d_exports as Content, types_d_exports$1 as Data, type Env, types_d_exports$2 as Head, type Log, types_d_exports$3 as Router, types_d_exports$4 as Spa, browserEnv, buildArticleHead, canonical, contentPlugin, createApp, createChannel, createIsland, createPlugin, createUrls, dataPlugin, defineRoutes, envPlugin, feedLink, headPlugin, hreflang, i18nPlugin, jsonLd, lazyEmbed, logPlugin, meta, og, route, routerPlugin, sitePlugin, spaPlugin, twitter };
|