foldkit-primitives 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/device/index.d.mts +3 -3
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/net/index.d.mts +51 -6
- package/dist/net/index.d.mts.map +1 -1
- package/dist/net/index.mjs +122 -1
- package/dist/net/index.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -146,7 +146,7 @@ reference: every Model shape, Message, argument, and failure rule.
|
|
|
146
146
|
| Subpath | What it covers | Primitives |
|
|
147
147
|
| --- | --- | --- |
|
|
148
148
|
| [`media`](./media/README.md) | the environment | `MediaQuery` (+ `PrefersDark`, `PrefersReducedMotion`), `Breakpoints`, `platformFromUA`, `isBrowser` |
|
|
149
|
-
| [`net`](./net/README.md) | the network | `Online`, `websocket`, `sse`, `broadcastMessages`, `postBroadcast` |
|
|
149
|
+
| [`net`](./net/README.md) | the network | `Online`, `websocket`, `sse`, `broadcastMessages`, `postBroadcast`, `SharedHost` |
|
|
150
150
|
| [`time`](./time/README.md) | the clock | `Timer`, `Interval`, `ticks`, `debounce`, `Throttle`, `formatRelativeTime` |
|
|
151
151
|
| [`state`](./state/README.md) | UI state with nowhere else to live | `Pagination`, `history`, `Locale`, `SelectionSet`, `Virtual`, `range`, layout math |
|
|
152
152
|
| [`motion`](./motion/README.md) | animation | `Tween`, `Spring`, `Presence`, the `Motion` service |
|
package/dist/device/index.d.mts
CHANGED
|
@@ -297,14 +297,14 @@ export declare const permissions: <const Name extends string>(config: {
|
|
|
297
297
|
readonly _tag: "Changed";
|
|
298
298
|
readonly name: string;
|
|
299
299
|
readonly state: "granted" | "denied" | "prompt";
|
|
300
|
-
} | {
|
|
301
|
-
readonly _tag: "Failed";
|
|
302
|
-
readonly message: string;
|
|
303
300
|
} | {
|
|
304
301
|
readonly _tag: "Snapshot";
|
|
305
302
|
readonly states: {
|
|
306
303
|
readonly [x: string]: "granted" | "denied" | "prompt";
|
|
307
304
|
};
|
|
305
|
+
} | {
|
|
306
|
+
readonly _tag: "Failed";
|
|
307
|
+
readonly message: string;
|
|
308
308
|
}, never, never, ManagedResource.ManagedResourceService<"permissions">, void, Readonly<{
|
|
309
309
|
watch: ManagedResource.Entry<PermissionsModel, PermissionsMessage, Option.Option<ReadonlyArray<string>>, Acquired, WatchService, (acquired: Acquired) => PermissionsMessage>;
|
|
310
310
|
}>, {}>;
|
package/dist/index.d.mts
CHANGED
|
@@ -5,8 +5,8 @@ import { a as modalityOfKey, i as Modality, n as InputModalityMessage, r as Inpu
|
|
|
5
5
|
import { ActiveElementMessage, Idle, IdleMessage, IdleModel, KeyPress, KeyboardMessage, PointerMessage, ScrollMessage, Visibility, VisibilityMessage, VisibilityModel, WindowSize, WindowSizeMessage, WindowSizeModel, activeElementEvents, keyboardEvents, matchHotkey, pointerEvents, scrollEvents } from "./events/index.mjs";
|
|
6
6
|
import { Breakpoints, BreakpointsMessage, BreakpointsModel, MediaQuery, MediaQueryMessage, MediaQueryModel, Platform, PlatformHints, PrefersDark, PrefersReducedMotion, breakpointFor, isBrowser, isServer, platformFromUA } from "./media/index.mjs";
|
|
7
7
|
import { a as SpringMessage, c as PresenceMessage, d as motion_d_exports, i as Spring, l as PresenceModel, n as TweenMessage, o as SpringModel, r as TweenModel, s as Presence, t as Tween, u as isVisible } from "./index-Cppf-lBx.mjs";
|
|
8
|
-
import { Acquired, AcquiredSource, BroadcastChannelHandle, BroadcastMessage, Online, OnlineMessage, OnlineModel, Socket, SocketHandle, SocketService, SocketView, Source, SourceHandle, SourceService, SseMessage, SseModel, SseView, WebSocketMessage, WebSocketModel, broadcastMessages, isLive, isOpen, postBroadcast, sse, viewOf, viewOfSse, websocket } from "./net/index.mjs";
|
|
8
|
+
import { Acquired, AcquiredSource, BroadcastChannelHandle, BroadcastMessage, Conversation, ConversationHandler, Online, OnlineMessage, OnlineModel, SharedHost, SharedHostConnection, SharedHostOptions, Socket, SocketHandle, SocketService, SocketView, Source, SourceHandle, SourceService, SseMessage, SseModel, SseView, StartHost, WebSocketMessage, WebSocketModel, broadcastMessages, isLive, isOpen, postBroadcast, sse, viewOf, viewOfSse, websocket } from "./net/index.mjs";
|
|
9
9
|
import { Bounds, Intersection, IntersectionChanged, Measured, Mutated, Mutation, Resize, Resized } from "./observers/index.mjs";
|
|
10
10
|
import { History, HistoryModel, Locale, LocaleMessage, LocaleModel, MasonryPlacement, MeasureRow, Pagination, PaginationMessage, PaginationModel, RowMeasured, SelectionSet, SelectionSetMessage, SelectionSetModel, Viewport, ViewportScrolled, Virtual, VirtualLayout, VirtualMessage, VirtualModel, VirtualWindow, canRedo, canUndo, distanceToEnd, history, isAtEnd, isSelected, masonry, offset, offsetFor, pageCount, range, stickyHeader, totalHeight, visibleRange, windowFor } from "./state/index.mjs";
|
|
11
11
|
import { Interval, IntervalMessage, IntervalModel, Throttle, ThrottleMessage, ThrottleModel, Throttled, Timer, TimerMessage, TimerModel, debounce, formatRelativeTime, ticks } from "./time/index.mjs";
|
|
12
|
-
export { Acquired, AcquiredSource, ActiveElementMessage, Autofocus, Bounds, Breakpoints, BreakpointsMessage, BreakpointsModel, BroadcastChannelHandle, BroadcastMessage, ClipboardMessage, ClipboardReadMessage, Coordinates, DRAG_THRESHOLD, DragCancelled, DragDropped, DragFact, DragPlace, DragStarted, DragZone, DraggedOver, EditAsked, EditableText, Fact, FocusScope, Focused, FollowTabStop, FullscreenMessage, Geolocation, GeolocationMessage, GeolocationModel, HideOutside, History, HistoryModel, Idle, IdleMessage, IdleModel, Input, InputMask, InputModality, InputModalityMessage, InputModalityModel, Intersection, IntersectionChanged, Interval, IntervalMessage, IntervalModel, KeepInView, KeepScrollOptions, KeyPress, KeyboardMessage, LiveStream, LiveStreamService, Locale, LocaleMessage, LocaleModel, MasonryPlacement, Measure, MeasureRow, Measured, MediaDevice, MediaDevicesHandle, MediaDevicesMessage, MediaDevicesModel, MediaQuery, MediaQueryMessage, MediaQueryModel, MediaStreamConstraints, MediaStreamHandle, MediaStreamMessage, MediaStreamModel, MediaTrackHandle, Modality, motion_d_exports as Motion, Move, MoveEnded, MoveStarted, Moved, Mutated, Mutation, Online, OnlineMessage, OnlineModel, Pagination, PaginationMessage, PaginationModel, PermissionState, PermissionStatusHandle, PermissionsHandle, PermissionsMessage, PermissionsModel, Platform, PlatformHints, PointerDrag, PointerMessage, PrefersDark, PrefersReducedMotion, Presence, PresenceMessage, PresenceModel, Resize, Resized, RowMeasured, ScriptMessage, ScrollLock, ScrollMessage, SelectionSet, SelectionSetMessage, SelectionSetModel, ShareData, ShareMessage, Socket, SocketHandle, SocketService, SocketView, Source, SourceHandle, SourceService, Spring, SpringMessage, SpringModel, SseMessage, SseModel, SseView, TargetFact, TargetHovered, TargetPressed, Targets, TextCancelled, TextCommitted, TextEdited, TextFact, Throttle, ThrottleMessage, ThrottleModel, Throttled, Timer, TimerMessage, TimerModel, Tween, TweenMessage, TweenModel, Viewport, ViewportScrolled, Virtual, VirtualLayout, VirtualMessage, VirtualModel, VirtualWindow, Visibility, VisibilityMessage, VisibilityModel, Watch, WatchService, WebSocketMessage, WebSocketModel, WindowSize, WindowSizeMessage, WindowSizeModel, activeElementEvents, applyMask, boxOf, breakpointFor, broadcastMessages, canRedo, canUndo, copyText, debounce, distanceToEnd, enterFullscreen, exitFullscreen, formatRelativeTime, fullscreenChanges, history, isAtEnd, isBrowser, isLive, isOpen, isSelected, isServer, isVisible, keepScroll, keyboardEvents, loadScript, masonry, matchHotkey, measured, mediaDevices, mediaStream, modalityOfKey, offset, offsetFor, pageCount, permissions, platformFromUA, pointerEvents, postBroadcast, range, readText, scrollEvents, share, sse, stickyHeader, tabbableWithin, targetOf, textOf, ticks, totalHeight, viewOf, viewOfSse, visibleRange, websocket, windowFor, zoneOf };
|
|
12
|
+
export { Acquired, AcquiredSource, ActiveElementMessage, Autofocus, Bounds, Breakpoints, BreakpointsMessage, BreakpointsModel, BroadcastChannelHandle, BroadcastMessage, ClipboardMessage, ClipboardReadMessage, Conversation, ConversationHandler, Coordinates, DRAG_THRESHOLD, DragCancelled, DragDropped, DragFact, DragPlace, DragStarted, DragZone, DraggedOver, EditAsked, EditableText, Fact, FocusScope, Focused, FollowTabStop, FullscreenMessage, Geolocation, GeolocationMessage, GeolocationModel, HideOutside, History, HistoryModel, Idle, IdleMessage, IdleModel, Input, InputMask, InputModality, InputModalityMessage, InputModalityModel, Intersection, IntersectionChanged, Interval, IntervalMessage, IntervalModel, KeepInView, KeepScrollOptions, KeyPress, KeyboardMessage, LiveStream, LiveStreamService, Locale, LocaleMessage, LocaleModel, MasonryPlacement, Measure, MeasureRow, Measured, MediaDevice, MediaDevicesHandle, MediaDevicesMessage, MediaDevicesModel, MediaQuery, MediaQueryMessage, MediaQueryModel, MediaStreamConstraints, MediaStreamHandle, MediaStreamMessage, MediaStreamModel, MediaTrackHandle, Modality, motion_d_exports as Motion, Move, MoveEnded, MoveStarted, Moved, Mutated, Mutation, Online, OnlineMessage, OnlineModel, Pagination, PaginationMessage, PaginationModel, PermissionState, PermissionStatusHandle, PermissionsHandle, PermissionsMessage, PermissionsModel, Platform, PlatformHints, PointerDrag, PointerMessage, PrefersDark, PrefersReducedMotion, Presence, PresenceMessage, PresenceModel, Resize, Resized, RowMeasured, ScriptMessage, ScrollLock, ScrollMessage, SelectionSet, SelectionSetMessage, SelectionSetModel, ShareData, ShareMessage, SharedHost, SharedHostConnection, SharedHostOptions, Socket, SocketHandle, SocketService, SocketView, Source, SourceHandle, SourceService, Spring, SpringMessage, SpringModel, SseMessage, SseModel, SseView, StartHost, TargetFact, TargetHovered, TargetPressed, Targets, TextCancelled, TextCommitted, TextEdited, TextFact, Throttle, ThrottleMessage, ThrottleModel, Throttled, Timer, TimerMessage, TimerModel, Tween, TweenMessage, TweenModel, Viewport, ViewportScrolled, Virtual, VirtualLayout, VirtualMessage, VirtualModel, VirtualWindow, Visibility, VisibilityMessage, VisibilityModel, Watch, WatchService, WebSocketMessage, WebSocketModel, WindowSize, WindowSizeMessage, WindowSizeModel, activeElementEvents, applyMask, boxOf, breakpointFor, broadcastMessages, canRedo, canUndo, copyText, debounce, distanceToEnd, enterFullscreen, exitFullscreen, formatRelativeTime, fullscreenChanges, history, isAtEnd, isBrowser, isLive, isOpen, isSelected, isServer, isVisible, keepScroll, keyboardEvents, loadScript, masonry, matchHotkey, measured, mediaDevices, mediaStream, modalityOfKey, offset, offsetFor, pageCount, permissions, platformFromUA, pointerEvents, postBroadcast, range, readText, scrollEvents, share, sse, stickyHeader, tabbableWithin, targetOf, textOf, ticks, totalHeight, viewOf, viewOfSse, visibleRange, websocket, windowFor, zoneOf };
|
package/dist/index.mjs
CHANGED
|
@@ -5,8 +5,8 @@ import { a as modalityOfKey, i as Modality, n as InputModalityMessage, r as Inpu
|
|
|
5
5
|
import { ActiveElementMessage, Idle, IdleMessage, IdleModel, KeyboardMessage, PointerMessage, ScrollMessage, Visibility, VisibilityMessage, VisibilityModel, WindowSize, WindowSizeMessage, WindowSizeModel, activeElementEvents, keyboardEvents, matchHotkey, pointerEvents, scrollEvents } from "./events/index.mjs";
|
|
6
6
|
import { Breakpoints, BreakpointsMessage, BreakpointsModel, MediaQuery, MediaQueryMessage, MediaQueryModel, Platform, PrefersDark, PrefersReducedMotion, breakpointFor, isBrowser, isServer, platformFromUA } from "./media/index.mjs";
|
|
7
7
|
import { a as SpringMessage, c as PresenceMessage, d as motion_exports, i as Spring, l as PresenceModel, n as TweenMessage, o as SpringModel, r as TweenModel, s as Presence, t as Tween, u as isVisible } from "./motion-DGGBZ0wt.mjs";
|
|
8
|
-
import { BroadcastMessage, Online, OnlineMessage, OnlineModel, Socket, SocketView, Source, SseMessage, SseModel, SseView, WebSocketMessage, WebSocketModel, broadcastMessages, isLive, isOpen, postBroadcast, sse, viewOf, viewOfSse, websocket } from "./net/index.mjs";
|
|
8
|
+
import { BroadcastMessage, Online, OnlineMessage, OnlineModel, SharedHost, Socket, SocketView, Source, SseMessage, SseModel, SseView, WebSocketMessage, WebSocketModel, broadcastMessages, isLive, isOpen, postBroadcast, sse, viewOf, viewOfSse, websocket } from "./net/index.mjs";
|
|
9
9
|
import { Bounds, Intersection, IntersectionChanged, Measured, Mutated, Mutation, Resize, Resized } from "./observers/index.mjs";
|
|
10
10
|
import { History, HistoryModel, Locale, LocaleMessage, LocaleModel, MeasureRow, Pagination, PaginationMessage, PaginationModel, RowMeasured, SelectionSet, SelectionSetMessage, SelectionSetModel, Viewport, ViewportScrolled, Virtual, VirtualMessage, VirtualModel, canRedo, canUndo, distanceToEnd, history, isAtEnd, isSelected, masonry, offset, offsetFor, pageCount, range, stickyHeader, totalHeight, visibleRange, windowFor } from "./state/index.mjs";
|
|
11
11
|
import { Interval, IntervalMessage, IntervalModel, Throttle, ThrottleMessage, ThrottleModel, Throttled, Timer, TimerMessage, TimerModel, debounce, formatRelativeTime, ticks } from "./time/index.mjs";
|
|
12
|
-
export { ActiveElementMessage, Autofocus, Bounds, Breakpoints, BreakpointsMessage, BreakpointsModel, BroadcastMessage, ClipboardMessage, ClipboardReadMessage, Coordinates, DRAG_THRESHOLD, DragCancelled, DragDropped, DragPlace, DragStarted, DragZone, DraggedOver, EditAsked, EditableText, FocusScope, Focused, FollowTabStop, FullscreenMessage, Geolocation, GeolocationMessage, GeolocationModel, HideOutside, History, HistoryModel, Idle, IdleMessage, IdleModel, Input, InputMask, InputModality, InputModalityMessage, InputModalityModel, Intersection, IntersectionChanged, Interval, IntervalMessage, IntervalModel, KeepInView, KeyboardMessage, LiveStream, Locale, LocaleMessage, LocaleModel, Measure, MeasureRow, Measured, MediaDevice, MediaDevicesMessage, MediaDevicesModel, MediaQuery, MediaQueryMessage, MediaQueryModel, MediaStreamMessage, MediaStreamModel, Modality, motion_exports as Motion, Move, MoveEnded, MoveStarted, Moved, Mutated, Mutation, Online, OnlineMessage, OnlineModel, Pagination, PaginationMessage, PaginationModel, PermissionState, PermissionsMessage, PermissionsModel, Platform, PointerDrag, PointerMessage, PrefersDark, PrefersReducedMotion, Presence, PresenceMessage, PresenceModel, Resize, Resized, RowMeasured, ScriptMessage, ScrollLock, ScrollMessage, SelectionSet, SelectionSetMessage, SelectionSetModel, ShareData, ShareMessage, Socket, SocketView, Source, Spring, SpringMessage, SpringModel, SseMessage, SseModel, SseView, TargetHovered, TargetPressed, Targets, TextCancelled, TextCommitted, TextEdited, Throttle, ThrottleMessage, ThrottleModel, Throttled, Timer, TimerMessage, TimerModel, Tween, TweenMessage, TweenModel, Viewport, ViewportScrolled, Virtual, VirtualMessage, VirtualModel, Visibility, VisibilityMessage, VisibilityModel, Watch, WebSocketMessage, WebSocketModel, WindowSize, WindowSizeMessage, WindowSizeModel, activeElementEvents, applyMask, boxOf, breakpointFor, broadcastMessages, canRedo, canUndo, copyText, debounce, distanceToEnd, enterFullscreen, exitFullscreen, formatRelativeTime, fullscreenChanges, history, isAtEnd, isBrowser, isLive, isOpen, isSelected, isServer, isVisible, keepScroll, keyboardEvents, loadScript, masonry, matchHotkey, measured, mediaDevices, mediaStream, modalityOfKey, offset, offsetFor, pageCount, permissions, platformFromUA, pointerEvents, postBroadcast, range, readText, scrollEvents, share, sse, stickyHeader, tabbableWithin, targetOf, textOf, ticks, totalHeight, viewOf, viewOfSse, visibleRange, websocket, windowFor, zoneOf };
|
|
12
|
+
export { ActiveElementMessage, Autofocus, Bounds, Breakpoints, BreakpointsMessage, BreakpointsModel, BroadcastMessage, ClipboardMessage, ClipboardReadMessage, Coordinates, DRAG_THRESHOLD, DragCancelled, DragDropped, DragPlace, DragStarted, DragZone, DraggedOver, EditAsked, EditableText, FocusScope, Focused, FollowTabStop, FullscreenMessage, Geolocation, GeolocationMessage, GeolocationModel, HideOutside, History, HistoryModel, Idle, IdleMessage, IdleModel, Input, InputMask, InputModality, InputModalityMessage, InputModalityModel, Intersection, IntersectionChanged, Interval, IntervalMessage, IntervalModel, KeepInView, KeyboardMessage, LiveStream, Locale, LocaleMessage, LocaleModel, Measure, MeasureRow, Measured, MediaDevice, MediaDevicesMessage, MediaDevicesModel, MediaQuery, MediaQueryMessage, MediaQueryModel, MediaStreamMessage, MediaStreamModel, Modality, motion_exports as Motion, Move, MoveEnded, MoveStarted, Moved, Mutated, Mutation, Online, OnlineMessage, OnlineModel, Pagination, PaginationMessage, PaginationModel, PermissionState, PermissionsMessage, PermissionsModel, Platform, PointerDrag, PointerMessage, PrefersDark, PrefersReducedMotion, Presence, PresenceMessage, PresenceModel, Resize, Resized, RowMeasured, ScriptMessage, ScrollLock, ScrollMessage, SelectionSet, SelectionSetMessage, SelectionSetModel, ShareData, ShareMessage, SharedHost, Socket, SocketView, Source, Spring, SpringMessage, SpringModel, SseMessage, SseModel, SseView, TargetHovered, TargetPressed, Targets, TextCancelled, TextCommitted, TextEdited, Throttle, ThrottleMessage, ThrottleModel, Throttled, Timer, TimerMessage, TimerModel, Tween, TweenMessage, TweenModel, Viewport, ViewportScrolled, Virtual, VirtualMessage, VirtualModel, Visibility, VisibilityMessage, VisibilityModel, Watch, WebSocketMessage, WebSocketModel, WindowSize, WindowSizeMessage, WindowSizeModel, activeElementEvents, applyMask, boxOf, breakpointFor, broadcastMessages, canRedo, canUndo, copyText, debounce, distanceToEnd, enterFullscreen, exitFullscreen, formatRelativeTime, fullscreenChanges, history, isAtEnd, isBrowser, isLive, isOpen, isSelected, isServer, isVisible, keepScroll, keyboardEvents, loadScript, masonry, matchHotkey, measured, mediaDevices, mediaStream, modalityOfKey, offset, offsetFor, pageCount, permissions, platformFromUA, pointerEvents, postBroadcast, range, readText, scrollEvents, share, sse, stickyHeader, tabbableWithin, targetOf, textOf, ticks, totalHeight, viewOf, viewOfSse, visibleRange, websocket, windowFor, zoneOf };
|
package/dist/net/index.d.mts
CHANGED
|
@@ -58,6 +58,51 @@ export declare const Online: Bundle.Bundle<"Online", void, {
|
|
|
58
58
|
readonly online: boolean;
|
|
59
59
|
}, never, never, never, void, {}, {}>;
|
|
60
60
|
//#endregion
|
|
61
|
+
//#region src/net/sharedHost.d.ts
|
|
62
|
+
/** One conversation a page opened: its port, and a signal that aborts once the page is gone. */
|
|
63
|
+
export interface Conversation {
|
|
64
|
+
readonly port: MessagePort;
|
|
65
|
+
readonly signal: AbortSignal;
|
|
66
|
+
}
|
|
67
|
+
/** What the host does with each conversation, told what the page opened it for. */
|
|
68
|
+
export type ConversationHandler<Opening> = (opening: Opening, conversation: Conversation) => void;
|
|
69
|
+
/** Starts the host, once, the first time a conversation needs it. */
|
|
70
|
+
export type StartHost<Opening> = () => ConversationHandler<Opening> | Promise<ConversationHandler<Opening>>;
|
|
71
|
+
export interface SharedHostOptions<Opening> {
|
|
72
|
+
/**
|
|
73
|
+
* The SharedWorker whose entry calls `serve`. Constructed by the
|
|
74
|
+
* application, since a bundler finds a worker only in a literal
|
|
75
|
+
* `new SharedWorker(new URL('./worker.ts', import.meta.url), …)`.
|
|
76
|
+
*/
|
|
77
|
+
readonly worker: () => {
|
|
78
|
+
readonly port: MessagePort;
|
|
79
|
+
};
|
|
80
|
+
/** The host, for a browser without SharedWorker: started in the top document. */
|
|
81
|
+
readonly inPage: StartHost<Opening>;
|
|
82
|
+
}
|
|
83
|
+
/** A page's way to the host. */
|
|
84
|
+
export interface SharedHostConnection<Opening> {
|
|
85
|
+
/** Opens a conversation for `opening`, returning the page's end of it. */
|
|
86
|
+
readonly open: (opening: Opening) => MessagePort;
|
|
87
|
+
}
|
|
88
|
+
export interface SharedHost<Opening> {
|
|
89
|
+
readonly name: string;
|
|
90
|
+
/** A SharedWorker's entry: hands the host every conversation a page opens. */
|
|
91
|
+
readonly serve: (start: StartHost<Opening>) => void;
|
|
92
|
+
/**
|
|
93
|
+
* A page's connection: through the SharedWorker, or, without one, to the
|
|
94
|
+
* host in the top document. Call it in the top document too when that
|
|
95
|
+
* document frames the pages, so that it is the one the frames meet.
|
|
96
|
+
*/
|
|
97
|
+
readonly connect: (options: SharedHostOptions<Opening>) => SharedHostConnection<Opening>;
|
|
98
|
+
}
|
|
99
|
+
export declare const SharedHost: {
|
|
100
|
+
define: <Opening, Encoded>(options: {
|
|
101
|
+
readonly name: string;
|
|
102
|
+
readonly opening: Schema.Codec<Opening, Encoded, never, never>;
|
|
103
|
+
}) => SharedHost<Opening>;
|
|
104
|
+
};
|
|
105
|
+
//#endregion
|
|
61
106
|
//#region src/net/sse.d.ts
|
|
62
107
|
/** The slice of an EventSource the bundle needs; satisfied by the platform class. */
|
|
63
108
|
export interface SourceHandle {
|
|
@@ -137,12 +182,12 @@ export declare const sse: <const Name extends string>(config: {
|
|
|
137
182
|
readonly _tag: "Opened";
|
|
138
183
|
} | {
|
|
139
184
|
readonly _tag: "Closed";
|
|
140
|
-
} | {
|
|
141
|
-
readonly _tag: "Received";
|
|
142
|
-
readonly data: string;
|
|
143
185
|
} | {
|
|
144
186
|
readonly _tag: "Failed";
|
|
145
187
|
readonly message: string;
|
|
188
|
+
} | {
|
|
189
|
+
readonly _tag: "Received";
|
|
190
|
+
readonly data: string;
|
|
146
191
|
}, never, ManagedResource.ManagedResourceService<"sse">, ManagedResource.ManagedResourceService<"sse">, void, Readonly<{
|
|
147
192
|
source: ManagedResource.Entry<SseModel, SseMessage, Option.Option<string>, AcquiredSource, SourceService, () => SseMessage>;
|
|
148
193
|
}>, {}>;
|
|
@@ -242,12 +287,12 @@ export declare const websocket: <const Name extends string>(config: {
|
|
|
242
287
|
readonly _tag: "Opened";
|
|
243
288
|
} | {
|
|
244
289
|
readonly _tag: "Closed";
|
|
245
|
-
} | {
|
|
246
|
-
readonly _tag: "Received";
|
|
247
|
-
readonly data: string;
|
|
248
290
|
} | {
|
|
249
291
|
readonly _tag: "Failed";
|
|
250
292
|
readonly message: string;
|
|
293
|
+
} | {
|
|
294
|
+
readonly _tag: "Received";
|
|
295
|
+
readonly data: string;
|
|
251
296
|
} | {
|
|
252
297
|
readonly _tag: "SendFailed";
|
|
253
298
|
readonly message: string;
|
package/dist/net/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/net/broadcast.ts","../../src/net/online.ts","../../src/net/sse.ts","../../src/net/websocket.ts"],"mappings":";;;;;;qBAWa,4CAAgB;;;;;;;;;;YAMjB,0BAA0B,iBAAiB;;iBAGtC;EACf,YAAY;EACZ;EACA,aAAa;aAAkB;;;KAG5B,kBAAkB,iBAAiB;;;;;;qBAmB3B,oBACX,cAEA;;AAAA,SAAS,+BACR,OAAO,OAAO;;;;;;;qBAyBJ,gBACX,cACA,eACA,SAAS,+BACR,QAAQ;;;qBCpEE,aAAW,OAAA;;;YACZ,qBAAqB,YAAY;qBAEhC,yCAAa;;qBAAb,OAAA;;;YACD,uBAAuB,cAAc;qBAkBpC,QAAM,OAAA;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/net/broadcast.ts","../../src/net/online.ts","../../src/net/sharedHost.ts","../../src/net/sse.ts","../../src/net/websocket.ts"],"mappings":";;;;;;qBAWa,4CAAgB;;;;;;;;;;YAMjB,0BAA0B,iBAAiB;;iBAGtC;EACf,YAAY;EACZ;EACA,aAAa;aAAkB;;;KAG5B,kBAAkB,iBAAiB;;;;;;qBAmB3B,oBACX,cAEA;;AAAA,SAAS,+BACR,OAAO,OAAO;;;;;;;qBAyBJ,gBACX,cACA,eACA,SAAS,+BACR,QAAQ;;;qBCpEE,aAAW,OAAA;;;YACZ,qBAAqB,YAAY;qBAEhC,yCAAa;;qBAAb,OAAA;;;YACD,uBAAuB,cAAc;qBAkBpC,QAAM,OAAA;;;;;;;;;iBClBF;WACN,MAAM;WACN,QAAQ;;;YAIP,oBAAoB,YAAY,SAAS,SAAS,cAAc;;YAGhE,UAAU,iBACpB,oBAAoB,WAAW,QAAQ,oBAAoB;iBAE5C,kBAAkB;;;;;;WAMxB;aAAyB,MAAM;;;WAE/B,QAAQ,UAAU;;;iBAIZ,qBAAqB;;WAE3B,OAAO,SAAS,YAAY;;iBAGtB,WAAW;WACjB;;WAEA,QAAQ,OAAO,UAAU;;;;;;WAMzB,UAAU,SAAS,kBAAkB,aAAa,qBAAqB;;qBA4IrE;EApIG,SAAA,SAAS,SAAO;aACrB;aACA,SAAS,OAAO,MAAM,SAAS;QACtC,WAAW;;;;;iBC/CE;WACN;EACT;EACA,UAAU;EACV,aAAa;EACb,WAAW;;qBAGA,UAAQ,OAAA;;;;;YAKT,kBAAkB,SAAS;qBAE1B,sCAAU;;;;;;;;;;;;YAQX,oBAAoB,WAAW;;;;;;;qBAQ9B,kCAAO;;;;;oBAAP,OAAA;;;YAMD,iBAAiB,QAAQ;;;;;qBAMxB,SAAU,OAAO;;;;;;qBAQjB,YAAa,OAAO,UAAU,oBAAkB;;iBAW5C;WACN,QAAQ;WACR,QAAQ,MAAM,MAAM;;;qBAIlB,QAAM,gBAAA,gBAAA,gBAAA,gBAAA;;YAGP,gBAAgB,gBAAgB,iBAAiB;qBAgBhD,YAAa,qBAAqB;WACpC,MAAM;;WAEN,iBAAiB,gBAAgB;MAC3C,OAAA,OAAA;WAOmB;;;;;;;;;;;;;;;;;;EAxBV,QAAA,gBAAgB,MACtB,UACA,YACA,OAAO,gBACP,gBACA,qBACM;;;;;iBC9EO;WACN;EACT,KAAK;EACL;EACA,UAAU;EACV,aAAa;EACb,WAAW;EACX,WAAW;;qBAGA,gBAAc,OAAA;;;;;;;YAOf,wBAAwB,eAAe;qBAEtC,4CAAgB;;;;;;;;;;;;;;;;;;;;;YAajB,0BAA0B,iBAAiB;;iBAGtC;WACN,QAAQ;WACR,QAAQ,MAAM,MAAM;;;;;;;;qBASlB,qCAAU;;;;;oBAAV,OAAA;;;YAMD,oBAAoB,WAAW;;;;;qBAM9B,SAAU,OAAO;;;;;;;qBASjB,SAAU,OAAO,gBAAgB,oBAAkB;;qBAWnD,QAAM,gBAAA,gBAAA,UAAA,gBAAA;;YAGP,gBAAgB,gBAAgB,iBAAiB;qBAyChD,kBAAmB,qBAAqB;WAC1C,MAAM;;WAEN,iBAAiB,gBAAgB;MAC3C,OAAA,OAAA;WAOmB;WAAoC;;;;;;;;;;;;;;;;;;;;;;;;;;;EAjD9C,QAAA,gBAAgB,MACtB,gBACA,kBACA,OAAO,gBACP,UACA,qBACM;;EAKF,OACJ,OAAO,gBACP,iBACG,OAAO,qBAAqB,gBAAgB,yBAAyB"}
|
package/dist/net/index.mjs
CHANGED
|
@@ -92,6 +92,127 @@ const Online = Bundle.make("Online", {
|
|
|
92
92
|
subscriptions: () => Subscription.make()(() => ({ changes: Subscription.persistent(presenceStream()) }))
|
|
93
93
|
});
|
|
94
94
|
//#endregion
|
|
95
|
+
//#region src/net/sharedHost.ts
|
|
96
|
+
/**
|
|
97
|
+
* One server in the browser for many documents: every tab and frame of an
|
|
98
|
+
* application meets the same host. A page opens conversations, each a
|
|
99
|
+
* `MessagePort` with a decoded opening that says what it is for; the host, in
|
|
100
|
+
* a SharedWorker shared by every tab, or in the top document where there is no
|
|
101
|
+
* SharedWorker, is handed each one with a signal that aborts when the page
|
|
102
|
+
* that opened it is gone.
|
|
103
|
+
*
|
|
104
|
+
* It owns the host's lifetime and the routing of conversations, nothing
|
|
105
|
+
* spoken over them: Sync's `portSocket` and Remote's `port` do that.
|
|
106
|
+
*/
|
|
107
|
+
/**
|
|
108
|
+
* A shared host: its `name`, which keeps two on one origin apart, and
|
|
109
|
+
* the schema of what opens a conversation, which the host decodes before
|
|
110
|
+
* anything else, ignoring a message that does not decode.
|
|
111
|
+
*/
|
|
112
|
+
const define = (options) => {
|
|
113
|
+
const { name } = options;
|
|
114
|
+
const Envelope = Schema.Struct({
|
|
115
|
+
host: Schema.Literal(name),
|
|
116
|
+
conversation: Schema.String,
|
|
117
|
+
opening: options.opening
|
|
118
|
+
});
|
|
119
|
+
const encode = Schema.encodeSync(Envelope);
|
|
120
|
+
const decode = Schema.decodeUnknownOption(Envelope);
|
|
121
|
+
const lockOf = (conversation) => `foldkit-shared-host:${name}:${conversation}`;
|
|
122
|
+
const marker = `foldkit-shared-host:${name}`;
|
|
123
|
+
const receive = (host, data, port) => Option.match(Option.zipWith(decode(data), Option.fromUndefinedOr(port), (envelope, given) => ({
|
|
124
|
+
envelope,
|
|
125
|
+
given
|
|
126
|
+
})), {
|
|
127
|
+
onNone: () => {},
|
|
128
|
+
onSome: ({ envelope, given }) => {
|
|
129
|
+
const ended = new AbortController();
|
|
130
|
+
Option.match(locks(), {
|
|
131
|
+
onNone: () => {},
|
|
132
|
+
onSome: (held) => {
|
|
133
|
+
held.request(lockOf(envelope.conversation), () => ended.abort());
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
host().then((started) => started(envelope.opening, {
|
|
137
|
+
port: given,
|
|
138
|
+
signal: ended.signal
|
|
139
|
+
})).catch(reportError);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
const once = (start) => {
|
|
143
|
+
let started;
|
|
144
|
+
return () => started ??= Promise.resolve().then(start);
|
|
145
|
+
};
|
|
146
|
+
const serve = (start) => {
|
|
147
|
+
const host = once(start);
|
|
148
|
+
self.addEventListener("connect", (event) => {
|
|
149
|
+
if (!(event instanceof MessageEvent)) return;
|
|
150
|
+
const [connection] = event.ports;
|
|
151
|
+
connection?.addEventListener("message", (message) => receive(host, message.data, message.ports[0]));
|
|
152
|
+
connection?.start();
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
const connect = ({ worker, inPage }) => {
|
|
156
|
+
const send = routeOf(worker, inPage);
|
|
157
|
+
return { open: (opening) => {
|
|
158
|
+
const { port1, port2 } = new MessageChannel();
|
|
159
|
+
const conversation = crypto.randomUUID();
|
|
160
|
+
const envelope = encode({
|
|
161
|
+
host: name,
|
|
162
|
+
conversation,
|
|
163
|
+
opening
|
|
164
|
+
});
|
|
165
|
+
hold(lockOf(conversation)).then(() => send(envelope, port2));
|
|
166
|
+
return port1;
|
|
167
|
+
} };
|
|
168
|
+
};
|
|
169
|
+
const routeOf = (worker, inPage) => {
|
|
170
|
+
if (typeof SharedWorker !== "undefined") {
|
|
171
|
+
const { port } = worker();
|
|
172
|
+
return (envelope, given) => port.postMessage(envelope, [given]);
|
|
173
|
+
}
|
|
174
|
+
return Option.match(acceptingTop(marker), {
|
|
175
|
+
onSome: (top) => (envelope, given) => top.postMessage(envelope, location.origin, [given]),
|
|
176
|
+
onNone: () => {
|
|
177
|
+
const host = once(inPage);
|
|
178
|
+
Reflect.set(window, marker, true);
|
|
179
|
+
window.addEventListener("message", (event) => {
|
|
180
|
+
if (event.origin === location.origin) receive(host, event.data, event.ports[0]);
|
|
181
|
+
});
|
|
182
|
+
return (envelope, given) => receive(host, envelope, given);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
return {
|
|
187
|
+
name,
|
|
188
|
+
serve,
|
|
189
|
+
connect
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
/** The top document, when it is another of this origin's and runs this host. */
|
|
193
|
+
const acceptingTop = (marker) => {
|
|
194
|
+
const top = window.top;
|
|
195
|
+
if (top === null || top === window) return Option.none();
|
|
196
|
+
try {
|
|
197
|
+
return Reflect.get(top, marker) === true ? Option.some(top) : Option.none();
|
|
198
|
+
} catch {
|
|
199
|
+
return Option.none();
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
/** Resolves once this document holds `lock`, which it then holds while it lives. */
|
|
203
|
+
const hold = (lock) => Option.match(locks(), {
|
|
204
|
+
onNone: () => Promise.resolve(),
|
|
205
|
+
onSome: (held) => new Promise((granted) => {
|
|
206
|
+
held.request(lock, () => {
|
|
207
|
+
granted();
|
|
208
|
+
return new Promise(() => {});
|
|
209
|
+
});
|
|
210
|
+
})
|
|
211
|
+
});
|
|
212
|
+
/** Web Locks, which a page outside a secure context, or an older browser, lacks. */
|
|
213
|
+
const locks = () => Option.fromUndefinedOr(navigator.locks);
|
|
214
|
+
const SharedHost = { define };
|
|
215
|
+
//#endregion
|
|
95
216
|
//#region src/net/sse.ts
|
|
96
217
|
/**
|
|
97
218
|
* Server-sent events as a bundle: connection lifecycle in the Model, the
|
|
@@ -407,6 +528,6 @@ const websocket = (config) => {
|
|
|
407
528
|
});
|
|
408
529
|
};
|
|
409
530
|
//#endregion
|
|
410
|
-
export { BroadcastMessage, Online, OnlineMessage, OnlineModel, Socket, SocketView, Source, SseMessage, SseModel, SseView, WebSocketMessage, WebSocketModel, broadcastMessages, isLive, isOpen, postBroadcast, sse, viewOf, viewOfSse, websocket };
|
|
531
|
+
export { BroadcastMessage, Online, OnlineMessage, OnlineModel, SharedHost, Socket, SocketView, Source, SseMessage, SseModel, SseView, WebSocketMessage, WebSocketModel, broadcastMessages, isLive, isOpen, postBroadcast, sse, viewOf, viewOfSse, websocket };
|
|
411
532
|
|
|
412
533
|
//# sourceMappingURL=index.mjs.map
|
package/dist/net/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["failMessage","failMessage"],"sources":["../../src/net/broadcast.ts","../../src/net/online.ts","../../src/net/sse.ts","../../src/net/websocket.ts"],"sourcesContent":["/**\n * Cross-tab messaging as an entry plus a Command — not a bundle, because the\n * channel owns no state: the parent maps `Received` into its own Message and\n * stores whatever it keeps. One assembly may open the same name twice (two\n * listeners); share the entry instead. By spec a post never echoes to its\n * own channel, so a tab never hears itself.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport type { Command } from 'foldkit/command'\nimport { defineMessageUnion } from 'foldkit/message'\n\nexport const BroadcastMessage = defineMessageUnion({\n /** Notifies without storing: project the payload into your own field to keep it. */\n Received: { data: Schema.Unknown },\n Posted: {},\n BroadcastFailed: { message: Schema.String },\n})\nexport type BroadcastMessage = typeof BroadcastMessage.Type\n\n/** The slice of a BroadcastChannel this module needs; satisfied by the platform class. */\nexport interface BroadcastChannelHandle {\n postMessage(data: unknown): void\n close(): void\n onmessage: ((event: { readonly data: unknown }) => void) | null\n}\n\ntype ChannelFactory = (name: string) => BroadcastChannelHandle\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nconst createChannel = (\n factory: ChannelFactory | undefined,\n name: string,\n): BroadcastChannelHandle | null => {\n if (factory !== undefined) return factory(name)\n if (typeof BroadcastChannel === 'undefined') return null\n return new BroadcastChannel(name) as unknown as BroadcastChannelHandle\n}\n\n/**\n * Every post to `name` from another channel, as Messages. Without a\n * BroadcastChannel API the stream is empty instead of throwing; closing the\n * stream closes the channel.\n */\nexport const broadcastMessages = (\n name: string,\n /** Read lazily so tests can substitute a double, like the SSE bundle. */\n create?: ChannelFactory | undefined,\n): Stream.Stream<BroadcastMessage> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const channel = createChannel(create, name)\n if (channel === null) return Stream.empty\n const queue = yield* Queue.unbounded<BroadcastMessage>()\n channel.onmessage = event => {\n Effect.runFork(Queue.offer(queue, BroadcastMessage.Received({ data: event.data })))\n }\n yield* Effect.addFinalizer(() =>\n Effect.sync(() => {\n channel.onmessage = null\n channel.close()\n }),\n )\n return Stream.fromQueue(queue)\n }),\n )\n\n/**\n * Posts one message to `name`. Opens a channel, posts, and closes it: the\n * send is one-shot, so it is a Command, not a resource. Without a\n * BroadcastChannel API — or when the post throws — it yields\n * `BroadcastFailed` instead of throwing.\n */\nexport const postBroadcast = (\n name: string,\n data: unknown,\n create?: ChannelFactory | undefined,\n): Command<BroadcastMessage, never, never> => ({\n name: 'Broadcast.post',\n args: { name, data },\n // One path for every failure: no factory and no platform API, or a post\n // that throws (a closed channel) all become BroadcastFailed.\n effect: Effect.matchEffect(\n Effect.try({\n try: () => {\n const channel = createChannel(create, name)\n if (channel === null) throw new Error('broadcast is unavailable')\n try {\n channel.postMessage(data)\n } finally {\n channel.close()\n }\n },\n catch: failMessage,\n }),\n {\n onFailure: message => Effect.succeed(BroadcastMessage.BroadcastFailed({ message })),\n onSuccess: () => Effect.succeed(BroadcastMessage.Posted()),\n },\n ),\n})\n","/**\n * Online presence as a bundle: `online` in the Model, kept current by the\n * window's online/offline events. The browser reports facts; the Model owns\n * the state, so presence replays like any other transition.\n */\nimport { Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const OnlineModel = Schema.Struct({ online: Schema.Boolean })\nexport type OnlineModel = typeof OnlineModel.Type\n\nexport const OnlineMessage = defineMessageUnion({ Changed: { online: Schema.Boolean } })\nexport type OnlineMessage = typeof OnlineMessage.Type\n\nconst presenceStream = (): Stream.Stream<OnlineMessage> => {\n // No window (SSR): nothing to listen to, so the slice keeps its default.\n if (typeof window === 'undefined') return Stream.empty\n return Stream.mergeAll(\n [\n Stream.fromEventListener(window, 'online').pipe(\n Stream.map(() => OnlineMessage.Changed({ online: true })),\n ),\n Stream.fromEventListener(window, 'offline').pipe(\n Stream.map(() => OnlineMessage.Changed({ online: false })),\n ),\n ],\n { concurrency: 'unbounded' },\n )\n}\n\nexport const Online = Bundle.make('Online', {\n Model: OnlineModel,\n Message: OnlineMessage,\n init: () => ({\n // navigator.onLine is the browser's current answer; true elsewhere (SSR).\n model: {\n online:\n typeof navigator !== 'undefined' && typeof navigator.onLine === 'boolean'\n ? navigator.onLine\n : true,\n },\n }),\n update: (model, message) =>\n message.online === model.online ? { model } : { model: { online: message.online } },\n subscriptions: (): Subscription.Subscriptions<OnlineModel, OnlineMessage> =>\n Subscription.make<OnlineModel, OnlineMessage>()(() => ({\n changes: Subscription.persistent(presenceStream()),\n })),\n})\n","/**\n * Server-sent events as a bundle: connection lifecycle in the Model, the\n * event stream through one Managed Resource. One-directional, so there is no\n * send: the server speaks, the Model listens. The browser reconnects dropped\n * streams itself; a Failed only records the error.\n */\nimport { Effect, Option, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { defineTaggedUnion } from 'foldkit/schema'\nimport * as ManagedResource from 'foldkit/managedResource'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\nimport { unlessSame } from '../internal.js'\n\n/** The slice of an EventSource the bundle needs; satisfied by the platform class. */\nexport interface SourceHandle {\n readonly readyState: number\n close(): void\n onopen: ((event: any) => void) | null\n onmessage: ((event: any) => void) | null\n onerror: ((event: any) => void) | null\n}\n\nexport const SseModel = Schema.Struct({\n url: Schema.String,\n status: Schema.Literals(['closed', 'connecting', 'open']),\n lastError: Schema.NullOr(Schema.String),\n})\nexport type SseModel = typeof SseModel.Type\n\nexport const SseMessage = defineMessageUnion({\n Connecting: {},\n Opened: {},\n /** Notifies without storing: project the payload into your own field to keep it. */\n Received: { data: Schema.String },\n Closed: {},\n Failed: { message: Schema.String },\n})\nexport type SseMessage = typeof SseMessage.Type\n\n/**\n * What the reader sees: the stream's state, or its error. A page derives\n * this instead of keeping its own connection state machine. `lastError`\n * carries the stored failure text: the runtime error sentence (`event stream\n * error for …`) or the acquire error's own message.\n */\nexport const SseView = defineTaggedUnion({\n Disconnected: {},\n Connecting: {},\n Connected: {},\n Error: { error: Schema.String },\n})\nexport type SseView = typeof SseView.Type\n\n/**\n * Whether the stream is live: open with no error outstanding. The browser\n * reconnects a dropped stream itself; a `Failed` only records the error.\n */\nexport const isLive = (model: SseModel): boolean =>\n model.status === 'open' && model.lastError === null\n\n/**\n * The view of a stream, and whether its page still wants it. An error stands\n * until the page stops wanting the stream; a retry still waiting for its\n * stream reads as connecting, so it answers at once.\n */\nexport const viewOfSse = (model: SseModel, wanted: boolean): SseView => {\n if (model.lastError !== null)\n return model.status === 'open' || !wanted\n ? SseView.Error({ error: model.lastError })\n : SseView.Connecting()\n if (model.status === 'open') return SseView.Connected()\n if (model.status === 'connecting' || wanted) return SseView.Connecting()\n return SseView.Disconnected()\n}\n\n/** The registry value behind the source tag: the live source and its incoming queue. */\nexport interface AcquiredSource {\n readonly source: SourceHandle\n readonly events: Queue.Queue<SseMessage>\n}\n\n/** The live stream, for the incoming subscription. One assembly holds one. */\nexport const Source = ManagedResource.tag<AcquiredSource>()('sse')\n\n/** The source service a placed SSE stream requires. */\nexport type SourceService = ManagedResource.ServiceOf<typeof Source>\n\ntype SourceResources = Readonly<{\n source: ManagedResource.Entry<\n SseModel,\n SseMessage,\n Option.Option<string>,\n AcquiredSource,\n SourceService,\n () => SseMessage\n >\n}>\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nexport const sse = <const Name extends string>(config: {\n readonly name: Name\n /** Defaults to the platform EventSource, read lazily so tests can substitute a double. */\n readonly createSource?: ((url: string) => SourceHandle) | undefined\n}) => {\n const createSource: (url: string) => SourceHandle =\n config.createSource ?? (url => new EventSource(url) as unknown as SourceHandle)\n return Bundle.make<\n Name,\n SseModel,\n SseMessage,\n { readonly url: string },\n never,\n SourceService,\n SourceService,\n void,\n SourceResources,\n {}\n >(config.name, {\n Model: SseModel,\n Message: SseMessage,\n args: Schema.Struct({ url: Schema.String }),\n init: (args): Update.Return<SseModel, SseMessage, SourceService> => ({\n model: { url: args.url, status: 'closed', lastError: null },\n }),\n update: (\n model,\n message,\n ): Update.ReturnWithOutMessage<SseModel, SseMessage, never, SourceService> =>\n SseMessage.match(message, {\n Connecting: () => ({ model: unlessSame(model, { ...model, status: 'connecting' }) }),\n Opened: () => ({\n model: unlessSame(model, { ...model, status: 'open', lastError: null }),\n }),\n Received: () => ({ model }),\n Closed: () => ({ model: unlessSame(model, { ...model, status: 'closed' }) }),\n // EventSource retries on its own, erroring the same way on every attempt.\n Failed: ({ message }) => ({\n model: unlessSame(model, { ...model, status: 'connecting', lastError: message }),\n }),\n }),\n resources: () =>\n ManagedResource.make<SseModel, SseMessage>()(entry => ({\n source: entry(Schema.Option(Schema.String), {\n resource: Source,\n modelToMaybeRequirements: model => Option.some(model.url),\n acquire: (url: string) =>\n Effect.gen(function* () {\n const events = yield* Queue.unbounded<SseMessage>()\n const source = createSource(url)\n // Offers run in arrival order; see the WebSocket bundle.\n let tail: Promise<void> = Promise.resolve()\n const enqueue = (effect: Effect.Effect<unknown>): void => {\n const run = () =>\n Effect.runPromise(effect).then(\n () => undefined,\n () => undefined,\n )\n tail = tail.then(run, run)\n }\n const offer = (message: SseMessage) => enqueue(Queue.offer(events, message))\n source.onopen = () => offer(SseMessage.Opened())\n // Event data is always text per the SSE spec.\n source.onmessage = event => offer(SseMessage.Received({ data: String(event.data) }))\n source.onerror = () =>\n offer(SseMessage.Failed({ message: `event stream error for ${url}` }))\n return { source, events }\n }),\n onAcquired: () => SseMessage.Connecting(),\n onReleased: () => SseMessage.Closed(),\n onAcquireError: error => SseMessage.Failed({ message: failMessage(error) }),\n // Daemon teardown: close the stream and drain the queue.\n release: ({ source, events }) =>\n Effect.asVoid(\n Effect.andThen(\n Effect.sync(() => {\n source.onopen = null\n source.onmessage = null\n source.onerror = null\n source.close()\n }),\n Queue.shutdown(events),\n ),\n ),\n }),\n })),\n subscriptions: (): Subscription.Subscriptions<SseModel, SseMessage, SourceService> =>\n Subscription.make<SseModel, SseMessage, SourceService>()(entry => ({\n incoming: entry(\n // Connecting to open is no reason to restart: the stream reads the\n // same queue, and replays an open it attached too late to see.\n { live: Schema.Boolean },\n {\n modelToDependencies: model => ({ live: model.status !== 'closed' }),\n dependenciesToStream: ({ live }) =>\n !live\n ? Stream.empty\n : Stream.unwrap(\n Effect.matchEffect(Source.get, {\n // Released between the dependency read and attach: end\n // quietly; onReleased reports Closed through update.\n onFailure: () => Effect.succeed(Stream.empty),\n onSuccess: ({ source, events }) =>\n Effect.succeed(\n Stream.concat(\n // A fast server opens before the stream attaches.\n // 1 is OPEN for EventSource everywhere.\n source.readyState === 1\n ? Stream.make(SseMessage.Opened())\n : Stream.empty,\n Stream.fromQueue(events),\n ),\n ),\n }),\n ),\n },\n ),\n })),\n })\n}\n","/**\n * A WebSocket as a bundle: connection lifecycle in the Model, full duplex\n * through one Managed Resource. The resource owns the socket; a `send`\n * helper writes through its tag service (lifted to the parent like every\n * helper); a subscription stream reads incoming messages through the same\n * tag. No hidden hub: everything the bundle touches is the Model, the\n * resource registry, or a Message.\n */\nimport { Effect, Option, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { defineTaggedUnion } from 'foldkit/schema'\nimport * as ManagedResource from 'foldkit/managedResource'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\nimport { unlessSame } from '../internal.js'\n\n/** The slice of a WebSocket the bundle needs; satisfied by the platform socket and test doubles. Handler events are `any`: the DOM and `ws` type them differently, and only `data` is read. */\nexport interface SocketHandle {\n readonly readyState: number\n send(data: string): void\n close(): void\n onopen: ((event: any) => void) | null\n onmessage: ((event: any) => void) | null\n onclose: ((event: any) => void) | null\n onerror: ((event: any) => void) | null\n}\n\nexport const WebSocketModel = Schema.Struct({\n url: Schema.String,\n status: Schema.Literals(['closed', 'connecting', 'open']),\n lastError: Schema.NullOr(Schema.String),\n /** Whether this connection attempt ever reached open; reset on Connecting. */\n opened: Schema.Boolean,\n})\nexport type WebSocketModel = typeof WebSocketModel.Type\n\nexport const WebSocketMessage = defineMessageUnion({\n Connecting: {},\n Opened: {},\n /** Notifies without storing: project the payload into your own field to keep it. */\n Received: { data: Schema.String },\n Closed: {},\n Failed: { message: Schema.String },\n SendFailed: { message: Schema.String },\n /** Acknowledges dispatch of `data`, not its delivery; update ignores it. */\n Sent: { data: Schema.String },\n /** The socket was still connecting when `connectTimeoutMs` ran out, and was closed. */\n TimedOut: {},\n})\nexport type WebSocketMessage = typeof WebSocketMessage.Type\n\n/** The registry value behind the socket tag: the live socket and its incoming queue. */\nexport interface Acquired {\n readonly socket: SocketHandle\n readonly events: Queue.Queue<WebSocketMessage>\n}\n\n/**\n * What the reader sees: the socket's state, or its error. A page derives\n * this instead of keeping its own connection state machine. `lastError` is\n * already worded for the reader (`Failed to connect to WebSocket`, never the\n * socket's own text, which error handling drops in favor of the sentence).\n */\nexport const SocketView = defineTaggedUnion({\n Disconnected: {},\n Connecting: {},\n Connected: {},\n Error: { error: Schema.String },\n})\nexport type SocketView = typeof SocketView.Type\n\n/**\n * Whether the socket is usable: open with no error outstanding. Gates sends\n * and received frames.\n */\nexport const isOpen = (model: WebSocketModel): boolean =>\n model.status === 'open' && model.lastError === null\n\n/**\n * The view of a socket, and whether its page still wants it. An error stands\n * until the page stops wanting the socket; a retry still waiting for its\n * socket reads as connecting, so it answers at once. An error on an open\n * socket always reads as an error.\n */\nexport const viewOf = (model: WebSocketModel, wanted: boolean): SocketView => {\n if (model.lastError !== null)\n return model.status === 'open' || !wanted\n ? SocketView.Error({ error: model.lastError })\n : SocketView.Connecting()\n if (model.status === 'open') return SocketView.Connected()\n if (model.status === 'connecting' || wanted) return SocketView.Connecting()\n return SocketView.Disconnected()\n}\n\n/** The live connection, for `send` and the incoming stream. One assembly holds one. */\nexport const Socket = ManagedResource.tag<Acquired>()('websocket')\n\n/** The socket service a placed WebSocket's streams and send commands require. */\nexport type SocketService = ManagedResource.ServiceOf<typeof Socket>\n\ntype SocketResources = Readonly<{\n socket: ManagedResource.Entry<\n WebSocketModel,\n WebSocketMessage,\n Option.Option<string>,\n Acquired,\n SocketService,\n () => WebSocketMessage\n >\n}>\n\ntype SendHelpers = Readonly<{\n send: (\n model: WebSocketModel,\n data: string,\n ) => Update.ReturnWithOutMessage<WebSocketModel, WebSocketMessage, never, SocketService>\n}>\n\nconst textOf = (data: unknown): Effect.Effect<string> =>\n typeof data === 'string'\n ? Effect.succeed(data)\n : data instanceof Blob\n ? Effect.promise(() => data.text())\n : data instanceof ArrayBuffer || ArrayBuffer.isView(data)\n ? Effect.succeed(new TextDecoder().decode(data))\n : Effect.succeed(String(data))\n\n/** Closes the socket with its handlers detached first, so the close reports nothing. */\nconst closeSilently = (socket: SocketHandle): void => {\n socket.onopen = null\n socket.onmessage = null\n socket.onclose = null\n socket.onerror = null\n socket.close()\n}\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nexport const websocket = <const Name extends string>(config: {\n readonly name: Name\n /** Defaults to the platform WebSocket, read lazily so tests can substitute a double. */\n readonly createSocket?: ((url: string) => SocketHandle) | undefined\n}) => {\n const createSocket: (url: string) => SocketHandle =\n config.createSocket ?? (url => new WebSocket(url) as unknown as SocketHandle)\n return Bundle.make<\n Name,\n WebSocketModel,\n WebSocketMessage,\n { readonly url: string; readonly connectTimeoutMs?: number | undefined },\n never,\n SocketService,\n SocketService,\n void,\n SocketResources,\n SendHelpers\n >(config.name, {\n Model: WebSocketModel,\n Message: WebSocketMessage,\n args: Schema.Struct({\n url: Schema.String,\n /** How long a socket may stay connecting before it is closed as `TimedOut`; unbounded when absent. */\n connectTimeoutMs: Schema.optional(\n Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())),\n ),\n }),\n init: (args): Update.Return<WebSocketModel, WebSocketMessage, SocketService> => ({\n model: { url: args.url, status: 'closed', lastError: null, opened: false },\n }),\n update: (\n model,\n message,\n ): Update.ReturnWithOutMessage<WebSocketModel, WebSocketMessage, never, SocketService> =>\n WebSocketMessage.match(message, {\n Connecting: () => ({\n model: unlessSame(model, {\n ...model,\n status: 'connecting',\n lastError: null,\n opened: false,\n }),\n }),\n Opened: () => ({\n model: unlessSame(model, { ...model, status: 'open', lastError: null, opened: true }),\n }),\n Received: () => ({ model }),\n // The close that follows an error, and the release after it, find it closed.\n Closed: () => ({ model: unlessSame(model, { ...model, status: 'closed' }) }),\n // Worded by whether the socket had opened: the socket's own text is\n // dropped in favor of the sentence, as a page would word it.\n Failed: () => ({\n model: unlessSame(model, {\n ...model,\n status: 'closed',\n lastError:\n model.status === 'open' ? 'Connection error' : 'Failed to connect to WebSocket',\n }),\n }),\n SendFailed: () => ({\n model: unlessSame(model, { ...model, lastError: 'Socket unavailable' }),\n }),\n Sent: () => ({ model }),\n TimedOut: () => ({\n model: unlessSame(model, {\n ...model,\n status: 'closed',\n lastError: 'Connection timeout',\n }),\n }),\n }),\n resources: () =>\n ManagedResource.make<WebSocketModel, WebSocketMessage>()(entry => ({\n socket: entry(Schema.Option(Schema.String), {\n resource: Socket,\n modelToMaybeRequirements: model => Option.some(model.url),\n acquire: (url: string) =>\n Effect.gen(function* () {\n const events = yield* Queue.unbounded<WebSocketMessage>()\n const socket = createSocket(url)\n // Offers run in arrival order: an async decode (Blob text) must\n // not overtake a later message. Rejections settle the chain\n // without breaking it; shutdown races are expected at teardown.\n let tail: Promise<void> = Promise.resolve()\n const enqueue = (effect: Effect.Effect<unknown>): void => {\n const run = () =>\n Effect.runPromise(effect).then(\n () => undefined,\n () => undefined,\n )\n tail = tail.then(run, run)\n }\n const offer = (message: WebSocketMessage) => enqueue(Queue.offer(events, message))\n socket.onopen = () => offer(WebSocketMessage.Opened())\n socket.onmessage = event => {\n enqueue(\n Effect.flatMap(textOf(event.data), text =>\n Queue.offer(events, WebSocketMessage.Received({ data: text })),\n ),\n )\n }\n socket.onclose = () => offer(WebSocketMessage.Closed())\n socket.onerror = () =>\n offer(WebSocketMessage.Failed({ message: `websocket error for ${url}` }))\n return { socket, events }\n }),\n onAcquired: () => WebSocketMessage.Connecting(),\n onReleased: () => WebSocketMessage.Closed(),\n onAcquireError: error => WebSocketMessage.Failed({ message: failMessage(error) }),\n // Daemon teardown: close the socket and drain the queue.\n release: ({ socket, events }) =>\n Effect.asVoid(\n Effect.andThen(\n Effect.sync(() => closeSilently(socket)),\n Queue.shutdown(events),\n ),\n ),\n }),\n })),\n subscriptions: ({\n connectTimeoutMs,\n }): Subscription.Subscriptions<WebSocketModel, WebSocketMessage, SocketService> =>\n Subscription.make<WebSocketModel, WebSocketMessage, SocketService>()(entry => ({\n connectTimeout: entry(\n { connecting: Schema.Boolean },\n {\n modelToDependencies: model => ({ connecting: model.status === 'connecting' }),\n dependenciesToStream: ({ connecting }) =>\n !connecting || connectTimeoutMs === undefined\n ? Stream.empty\n : Stream.unwrap(\n Effect.matchEffect(Effect.andThen(Effect.sleep(connectTimeoutMs), Socket.get), {\n // Released while waiting: onReleased reports Closed.\n onFailure: () => Effect.succeed(Stream.empty),\n onSuccess: ({ socket }) =>\n Effect.sync((): Stream.Stream<WebSocketMessage> => {\n // 0 is WebSocket.CONNECTING. An open that beat the\n // timer wins; the handlers go before the close, so\n // the error and close it causes report nothing.\n if (socket.readyState !== 0) return Stream.empty\n closeSilently(socket)\n return Stream.make(WebSocketMessage.TimedOut())\n }),\n }),\n ),\n },\n ),\n incoming: entry(\n // Connecting to open is no reason to restart: the stream reads the\n // same queue, and replays an open it attached too late to see.\n { live: Schema.Boolean },\n {\n modelToDependencies: model => ({ live: model.status !== 'closed' }),\n dependenciesToStream: ({ live }) =>\n !live\n ? Stream.empty\n : Stream.unwrap(\n Effect.matchEffect(Socket.get, {\n // Released between the dependency read and attach: end\n // quietly; onReleased reports Closed through update.\n onFailure: () => Effect.succeed(Stream.empty),\n onSuccess: ({ socket, events }) =>\n Effect.succeed(\n Stream.concat(\n // A fast server opens before the stream attaches; without\n // this the status would stick at connecting while\n // messages flow. 1 is WebSocket.OPEN everywhere.\n socket.readyState === 1\n ? Stream.make(WebSocketMessage.Opened())\n : Stream.empty,\n Stream.fromQueue(events),\n ),\n ),\n }),\n ),\n },\n ),\n })),\n helpers: {\n send: (model, data) => ({\n model,\n commands: [\n {\n name: 'WebSocket.send',\n args: { data },\n effect: Effect.matchEffect(\n Effect.flatMap(Socket.get, ({ socket }) =>\n // A closed socket's send is a silent no-op per spec (only a\n // connecting socket throws), so check first: SendFailed either way.\n socket.readyState === 1\n ? Effect.map(\n Effect.sync(() => {\n socket.send(data)\n }),\n () => WebSocketMessage.Sent({ data }),\n )\n : Effect.fail(\n new Error(\n `cannot send: socket is not open (readyState ${socket.readyState})`,\n ),\n ),\n ),\n {\n onFailure: error =>\n Effect.succeed(WebSocketMessage.SendFailed({ message: failMessage(error) })),\n onSuccess: message => Effect.succeed(message),\n },\n ),\n },\n ],\n }),\n },\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["failMessage","failMessage"],"sources":["../../src/net/broadcast.ts","../../src/net/online.ts","../../src/net/sharedHost.ts","../../src/net/sse.ts","../../src/net/websocket.ts"],"sourcesContent":["/**\n * Cross-tab messaging as an entry plus a Command — not a bundle, because the\n * channel owns no state: the parent maps `Received` into its own Message and\n * stores whatever it keeps. One assembly may open the same name twice (two\n * listeners); share the entry instead. By spec a post never echoes to its\n * own channel, so a tab never hears itself.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport type { Command } from 'foldkit/command'\nimport { defineMessageUnion } from 'foldkit/message'\n\nexport const BroadcastMessage = defineMessageUnion({\n /** Notifies without storing: project the payload into your own field to keep it. */\n Received: { data: Schema.Unknown },\n Posted: {},\n BroadcastFailed: { message: Schema.String },\n})\nexport type BroadcastMessage = typeof BroadcastMessage.Type\n\n/** The slice of a BroadcastChannel this module needs; satisfied by the platform class. */\nexport interface BroadcastChannelHandle {\n postMessage(data: unknown): void\n close(): void\n onmessage: ((event: { readonly data: unknown }) => void) | null\n}\n\ntype ChannelFactory = (name: string) => BroadcastChannelHandle\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nconst createChannel = (\n factory: ChannelFactory | undefined,\n name: string,\n): BroadcastChannelHandle | null => {\n if (factory !== undefined) return factory(name)\n if (typeof BroadcastChannel === 'undefined') return null\n return new BroadcastChannel(name) as unknown as BroadcastChannelHandle\n}\n\n/**\n * Every post to `name` from another channel, as Messages. Without a\n * BroadcastChannel API the stream is empty instead of throwing; closing the\n * stream closes the channel.\n */\nexport const broadcastMessages = (\n name: string,\n /** Read lazily so tests can substitute a double, like the SSE bundle. */\n create?: ChannelFactory | undefined,\n): Stream.Stream<BroadcastMessage> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const channel = createChannel(create, name)\n if (channel === null) return Stream.empty\n const queue = yield* Queue.unbounded<BroadcastMessage>()\n channel.onmessage = event => {\n Effect.runFork(Queue.offer(queue, BroadcastMessage.Received({ data: event.data })))\n }\n yield* Effect.addFinalizer(() =>\n Effect.sync(() => {\n channel.onmessage = null\n channel.close()\n }),\n )\n return Stream.fromQueue(queue)\n }),\n )\n\n/**\n * Posts one message to `name`. Opens a channel, posts, and closes it: the\n * send is one-shot, so it is a Command, not a resource. Without a\n * BroadcastChannel API — or when the post throws — it yields\n * `BroadcastFailed` instead of throwing.\n */\nexport const postBroadcast = (\n name: string,\n data: unknown,\n create?: ChannelFactory | undefined,\n): Command<BroadcastMessage, never, never> => ({\n name: 'Broadcast.post',\n args: { name, data },\n // One path for every failure: no factory and no platform API, or a post\n // that throws (a closed channel) all become BroadcastFailed.\n effect: Effect.matchEffect(\n Effect.try({\n try: () => {\n const channel = createChannel(create, name)\n if (channel === null) throw new Error('broadcast is unavailable')\n try {\n channel.postMessage(data)\n } finally {\n channel.close()\n }\n },\n catch: failMessage,\n }),\n {\n onFailure: message => Effect.succeed(BroadcastMessage.BroadcastFailed({ message })),\n onSuccess: () => Effect.succeed(BroadcastMessage.Posted()),\n },\n ),\n})\n","/**\n * Online presence as a bundle: `online` in the Model, kept current by the\n * window's online/offline events. The browser reports facts; the Model owns\n * the state, so presence replays like any other transition.\n */\nimport { Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const OnlineModel = Schema.Struct({ online: Schema.Boolean })\nexport type OnlineModel = typeof OnlineModel.Type\n\nexport const OnlineMessage = defineMessageUnion({ Changed: { online: Schema.Boolean } })\nexport type OnlineMessage = typeof OnlineMessage.Type\n\nconst presenceStream = (): Stream.Stream<OnlineMessage> => {\n // No window (SSR): nothing to listen to, so the slice keeps its default.\n if (typeof window === 'undefined') return Stream.empty\n return Stream.mergeAll(\n [\n Stream.fromEventListener(window, 'online').pipe(\n Stream.map(() => OnlineMessage.Changed({ online: true })),\n ),\n Stream.fromEventListener(window, 'offline').pipe(\n Stream.map(() => OnlineMessage.Changed({ online: false })),\n ),\n ],\n { concurrency: 'unbounded' },\n )\n}\n\nexport const Online = Bundle.make('Online', {\n Model: OnlineModel,\n Message: OnlineMessage,\n init: () => ({\n // navigator.onLine is the browser's current answer; true elsewhere (SSR).\n model: {\n online:\n typeof navigator !== 'undefined' && typeof navigator.onLine === 'boolean'\n ? navigator.onLine\n : true,\n },\n }),\n update: (model, message) =>\n message.online === model.online ? { model } : { model: { online: message.online } },\n subscriptions: (): Subscription.Subscriptions<OnlineModel, OnlineMessage> =>\n Subscription.make<OnlineModel, OnlineMessage>()(() => ({\n changes: Subscription.persistent(presenceStream()),\n })),\n})\n","/**\n * One server in the browser for many documents: every tab and frame of an\n * application meets the same host. A page opens conversations, each a\n * `MessagePort` with a decoded opening that says what it is for; the host, in\n * a SharedWorker shared by every tab, or in the top document where there is no\n * SharedWorker, is handed each one with a signal that aborts when the page\n * that opened it is gone.\n *\n * It owns the host's lifetime and the routing of conversations, nothing\n * spoken over them: Sync's `portSocket` and Remote's `port` do that.\n */\nimport { Option, Schema } from 'effect'\n\n/** One conversation a page opened: its port, and a signal that aborts once the page is gone. */\nexport interface Conversation {\n readonly port: MessagePort\n readonly signal: AbortSignal\n}\n\n/** What the host does with each conversation, told what the page opened it for. */\nexport type ConversationHandler<Opening> = (opening: Opening, conversation: Conversation) => void\n\n/** Starts the host, once, the first time a conversation needs it. */\nexport type StartHost<Opening> = () =>\n ConversationHandler<Opening> | Promise<ConversationHandler<Opening>>\n\nexport interface SharedHostOptions<Opening> {\n /**\n * The SharedWorker whose entry calls `serve`. Constructed by the\n * application, since a bundler finds a worker only in a literal\n * `new SharedWorker(new URL('./worker.ts', import.meta.url), …)`.\n */\n readonly worker: () => { readonly port: MessagePort }\n /** The host, for a browser without SharedWorker: started in the top document. */\n readonly inPage: StartHost<Opening>\n}\n\n/** A page's way to the host. */\nexport interface SharedHostConnection<Opening> {\n /** Opens a conversation for `opening`, returning the page's end of it. */\n readonly open: (opening: Opening) => MessagePort\n}\n\nexport interface SharedHost<Opening> {\n readonly name: string\n /** A SharedWorker's entry: hands the host every conversation a page opens. */\n readonly serve: (start: StartHost<Opening>) => void\n /**\n * A page's connection: through the SharedWorker, or, without one, to the\n * host in the top document. Call it in the top document too when that\n * document frames the pages, so that it is the one the frames meet.\n */\n readonly connect: (options: SharedHostOptions<Opening>) => SharedHostConnection<Opening>\n}\n\n/**\n * A shared host: its `name`, which keeps two on one origin apart, and\n * the schema of what opens a conversation, which the host decodes before\n * anything else, ignoring a message that does not decode.\n */\nconst define = <Opening, Encoded>(options: {\n readonly name: string\n readonly opening: Schema.Codec<Opening, Encoded, never, never>\n}): SharedHost<Opening> => {\n const { name } = options\n const Envelope = Schema.Struct({\n host: Schema.Literal(name),\n conversation: Schema.String,\n opening: options.opening,\n })\n const encode = Schema.encodeSync(Envelope)\n const decode = Schema.decodeUnknownOption(Envelope)\n const lockOf = (conversation: string) => `foldkit-shared-host:${name}:${conversation}`\n const marker = `foldkit-shared-host:${name}`\n\n const receive = (\n host: () => Promise<ConversationHandler<Opening>>,\n data: unknown,\n port: MessagePort | undefined,\n ) =>\n Option.match(\n Option.zipWith(decode(data), Option.fromUndefinedOr(port), (envelope, given) => ({\n envelope,\n given,\n })),\n {\n // Not a page's opening, or one without its port: nothing to answer.\n onNone: () => {},\n onSome: ({ envelope, given }) => {\n const ended = new AbortController()\n // The page holds this lock while it lives, so the host is granted it\n // when the page's document unloads or crashes.\n Option.match(locks(), {\n onNone: () => {},\n onSome: held => {\n void held.request(lockOf(envelope.conversation), () => ended.abort())\n },\n })\n void host()\n .then(started => started(envelope.opening, { port: given, signal: ended.signal }))\n .catch(reportError)\n },\n },\n )\n\n const once = (start: StartHost<Opening>): (() => Promise<ConversationHandler<Opening>>) => {\n let started: Promise<ConversationHandler<Opening>> | undefined\n return () => (started ??= Promise.resolve().then(start))\n }\n\n const serve = (start: StartHost<Opening>): void => {\n const host = once(start)\n self.addEventListener('connect', event => {\n if (!(event instanceof MessageEvent)) return\n const [connection] = event.ports\n connection?.addEventListener('message', message =>\n receive(host, message.data, message.ports[0]),\n )\n connection?.start()\n })\n }\n\n const connect = ({\n worker,\n inPage,\n }: SharedHostOptions<Opening>): SharedHostConnection<Opening> => {\n const send = routeOf(worker, inPage)\n return {\n open: opening => {\n const { port1, port2 } = new MessageChannel()\n const conversation = crypto.randomUUID()\n const envelope = encode({ host: name, conversation, opening })\n // The opening goes once the lock is held, so the host cannot be\n // granted it first and end the conversation before it began.\n void hold(lockOf(conversation)).then(() => send(envelope, port2))\n return port1\n },\n }\n }\n\n const routeOf = (\n worker: SharedHostOptions<Opening>['worker'],\n inPage: StartHost<Opening>,\n ): ((envelope: unknown, port: MessagePort) => void) => {\n if (typeof SharedWorker !== 'undefined') {\n const { port } = worker()\n return (envelope, given) => port.postMessage(envelope, [given])\n }\n return Option.match(acceptingTop(marker), {\n onSome: top => (envelope, given) => top.postMessage(envelope, location.origin, [given]),\n onNone: () => {\n const host = once(inPage)\n Reflect.set(window, marker, true)\n window.addEventListener('message', event => {\n if (event.origin === location.origin) receive(host, event.data, event.ports[0])\n })\n return (envelope, given) => receive(host, envelope, given)\n },\n })\n }\n\n return { name, serve, connect }\n}\n\n/** The top document, when it is another of this origin's and runs this host. */\nconst acceptingTop = (marker: string): Option.Option<Window> => {\n const top = window.top\n if (top === null || top === window) return Option.none()\n try {\n return Reflect.get(top, marker) === true ? Option.some(top) : Option.none()\n } catch {\n // Another origin's top document: reading it throws.\n return Option.none()\n }\n}\n\n/** Resolves once this document holds `lock`, which it then holds while it lives. */\nconst hold = (lock: string): Promise<void> =>\n Option.match(locks(), {\n onNone: () => Promise.resolve(),\n onSome: held =>\n new Promise(granted => {\n void held.request(lock, () => {\n granted()\n return new Promise(() => {})\n })\n }),\n })\n\n/** Web Locks, which a page outside a secure context, or an older browser, lacks. */\nconst locks = (): Option.Option<LockManager> => Option.fromUndefinedOr(navigator.locks)\n\nexport const SharedHost = { define }\n","/**\n * Server-sent events as a bundle: connection lifecycle in the Model, the\n * event stream through one Managed Resource. One-directional, so there is no\n * send: the server speaks, the Model listens. The browser reconnects dropped\n * streams itself; a Failed only records the error.\n */\nimport { Effect, Option, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { defineTaggedUnion } from 'foldkit/schema'\nimport * as ManagedResource from 'foldkit/managedResource'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\nimport { unlessSame } from '../internal.js'\n\n/** The slice of an EventSource the bundle needs; satisfied by the platform class. */\nexport interface SourceHandle {\n readonly readyState: number\n close(): void\n onopen: ((event: any) => void) | null\n onmessage: ((event: any) => void) | null\n onerror: ((event: any) => void) | null\n}\n\nexport const SseModel = Schema.Struct({\n url: Schema.String,\n status: Schema.Literals(['closed', 'connecting', 'open']),\n lastError: Schema.NullOr(Schema.String),\n})\nexport type SseModel = typeof SseModel.Type\n\nexport const SseMessage = defineMessageUnion({\n Connecting: {},\n Opened: {},\n /** Notifies without storing: project the payload into your own field to keep it. */\n Received: { data: Schema.String },\n Closed: {},\n Failed: { message: Schema.String },\n})\nexport type SseMessage = typeof SseMessage.Type\n\n/**\n * What the reader sees: the stream's state, or its error. A page derives\n * this instead of keeping its own connection state machine. `lastError`\n * carries the stored failure text: the runtime error sentence (`event stream\n * error for …`) or the acquire error's own message.\n */\nexport const SseView = defineTaggedUnion({\n Disconnected: {},\n Connecting: {},\n Connected: {},\n Error: { error: Schema.String },\n})\nexport type SseView = typeof SseView.Type\n\n/**\n * Whether the stream is live: open with no error outstanding. The browser\n * reconnects a dropped stream itself; a `Failed` only records the error.\n */\nexport const isLive = (model: SseModel): boolean =>\n model.status === 'open' && model.lastError === null\n\n/**\n * The view of a stream, and whether its page still wants it. An error stands\n * until the page stops wanting the stream; a retry still waiting for its\n * stream reads as connecting, so it answers at once.\n */\nexport const viewOfSse = (model: SseModel, wanted: boolean): SseView => {\n if (model.lastError !== null)\n return model.status === 'open' || !wanted\n ? SseView.Error({ error: model.lastError })\n : SseView.Connecting()\n if (model.status === 'open') return SseView.Connected()\n if (model.status === 'connecting' || wanted) return SseView.Connecting()\n return SseView.Disconnected()\n}\n\n/** The registry value behind the source tag: the live source and its incoming queue. */\nexport interface AcquiredSource {\n readonly source: SourceHandle\n readonly events: Queue.Queue<SseMessage>\n}\n\n/** The live stream, for the incoming subscription. One assembly holds one. */\nexport const Source = ManagedResource.tag<AcquiredSource>()('sse')\n\n/** The source service a placed SSE stream requires. */\nexport type SourceService = ManagedResource.ServiceOf<typeof Source>\n\ntype SourceResources = Readonly<{\n source: ManagedResource.Entry<\n SseModel,\n SseMessage,\n Option.Option<string>,\n AcquiredSource,\n SourceService,\n () => SseMessage\n >\n}>\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nexport const sse = <const Name extends string>(config: {\n readonly name: Name\n /** Defaults to the platform EventSource, read lazily so tests can substitute a double. */\n readonly createSource?: ((url: string) => SourceHandle) | undefined\n}) => {\n const createSource: (url: string) => SourceHandle =\n config.createSource ?? (url => new EventSource(url) as unknown as SourceHandle)\n return Bundle.make<\n Name,\n SseModel,\n SseMessage,\n { readonly url: string },\n never,\n SourceService,\n SourceService,\n void,\n SourceResources,\n {}\n >(config.name, {\n Model: SseModel,\n Message: SseMessage,\n args: Schema.Struct({ url: Schema.String }),\n init: (args): Update.Return<SseModel, SseMessage, SourceService> => ({\n model: { url: args.url, status: 'closed', lastError: null },\n }),\n update: (\n model,\n message,\n ): Update.ReturnWithOutMessage<SseModel, SseMessage, never, SourceService> =>\n SseMessage.match(message, {\n Connecting: () => ({ model: unlessSame(model, { ...model, status: 'connecting' }) }),\n Opened: () => ({\n model: unlessSame(model, { ...model, status: 'open', lastError: null }),\n }),\n Received: () => ({ model }),\n Closed: () => ({ model: unlessSame(model, { ...model, status: 'closed' }) }),\n // EventSource retries on its own, erroring the same way on every attempt.\n Failed: ({ message }) => ({\n model: unlessSame(model, { ...model, status: 'connecting', lastError: message }),\n }),\n }),\n resources: () =>\n ManagedResource.make<SseModel, SseMessage>()(entry => ({\n source: entry(Schema.Option(Schema.String), {\n resource: Source,\n modelToMaybeRequirements: model => Option.some(model.url),\n acquire: (url: string) =>\n Effect.gen(function* () {\n const events = yield* Queue.unbounded<SseMessage>()\n const source = createSource(url)\n // Offers run in arrival order; see the WebSocket bundle.\n let tail: Promise<void> = Promise.resolve()\n const enqueue = (effect: Effect.Effect<unknown>): void => {\n const run = () =>\n Effect.runPromise(effect).then(\n () => undefined,\n () => undefined,\n )\n tail = tail.then(run, run)\n }\n const offer = (message: SseMessage) => enqueue(Queue.offer(events, message))\n source.onopen = () => offer(SseMessage.Opened())\n // Event data is always text per the SSE spec.\n source.onmessage = event => offer(SseMessage.Received({ data: String(event.data) }))\n source.onerror = () =>\n offer(SseMessage.Failed({ message: `event stream error for ${url}` }))\n return { source, events }\n }),\n onAcquired: () => SseMessage.Connecting(),\n onReleased: () => SseMessage.Closed(),\n onAcquireError: error => SseMessage.Failed({ message: failMessage(error) }),\n // Daemon teardown: close the stream and drain the queue.\n release: ({ source, events }) =>\n Effect.asVoid(\n Effect.andThen(\n Effect.sync(() => {\n source.onopen = null\n source.onmessage = null\n source.onerror = null\n source.close()\n }),\n Queue.shutdown(events),\n ),\n ),\n }),\n })),\n subscriptions: (): Subscription.Subscriptions<SseModel, SseMessage, SourceService> =>\n Subscription.make<SseModel, SseMessage, SourceService>()(entry => ({\n incoming: entry(\n // Connecting to open is no reason to restart: the stream reads the\n // same queue, and replays an open it attached too late to see.\n { live: Schema.Boolean },\n {\n modelToDependencies: model => ({ live: model.status !== 'closed' }),\n dependenciesToStream: ({ live }) =>\n !live\n ? Stream.empty\n : Stream.unwrap(\n Effect.matchEffect(Source.get, {\n // Released between the dependency read and attach: end\n // quietly; onReleased reports Closed through update.\n onFailure: () => Effect.succeed(Stream.empty),\n onSuccess: ({ source, events }) =>\n Effect.succeed(\n Stream.concat(\n // A fast server opens before the stream attaches.\n // 1 is OPEN for EventSource everywhere.\n source.readyState === 1\n ? Stream.make(SseMessage.Opened())\n : Stream.empty,\n Stream.fromQueue(events),\n ),\n ),\n }),\n ),\n },\n ),\n })),\n })\n}\n","/**\n * A WebSocket as a bundle: connection lifecycle in the Model, full duplex\n * through one Managed Resource. The resource owns the socket; a `send`\n * helper writes through its tag service (lifted to the parent like every\n * helper); a subscription stream reads incoming messages through the same\n * tag. No hidden hub: everything the bundle touches is the Model, the\n * resource registry, or a Message.\n */\nimport { Effect, Option, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { defineTaggedUnion } from 'foldkit/schema'\nimport * as ManagedResource from 'foldkit/managedResource'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\nimport { unlessSame } from '../internal.js'\n\n/** The slice of a WebSocket the bundle needs; satisfied by the platform socket and test doubles. Handler events are `any`: the DOM and `ws` type them differently, and only `data` is read. */\nexport interface SocketHandle {\n readonly readyState: number\n send(data: string): void\n close(): void\n onopen: ((event: any) => void) | null\n onmessage: ((event: any) => void) | null\n onclose: ((event: any) => void) | null\n onerror: ((event: any) => void) | null\n}\n\nexport const WebSocketModel = Schema.Struct({\n url: Schema.String,\n status: Schema.Literals(['closed', 'connecting', 'open']),\n lastError: Schema.NullOr(Schema.String),\n /** Whether this connection attempt ever reached open; reset on Connecting. */\n opened: Schema.Boolean,\n})\nexport type WebSocketModel = typeof WebSocketModel.Type\n\nexport const WebSocketMessage = defineMessageUnion({\n Connecting: {},\n Opened: {},\n /** Notifies without storing: project the payload into your own field to keep it. */\n Received: { data: Schema.String },\n Closed: {},\n Failed: { message: Schema.String },\n SendFailed: { message: Schema.String },\n /** Acknowledges dispatch of `data`, not its delivery; update ignores it. */\n Sent: { data: Schema.String },\n /** The socket was still connecting when `connectTimeoutMs` ran out, and was closed. */\n TimedOut: {},\n})\nexport type WebSocketMessage = typeof WebSocketMessage.Type\n\n/** The registry value behind the socket tag: the live socket and its incoming queue. */\nexport interface Acquired {\n readonly socket: SocketHandle\n readonly events: Queue.Queue<WebSocketMessage>\n}\n\n/**\n * What the reader sees: the socket's state, or its error. A page derives\n * this instead of keeping its own connection state machine. `lastError` is\n * already worded for the reader (`Failed to connect to WebSocket`, never the\n * socket's own text, which error handling drops in favor of the sentence).\n */\nexport const SocketView = defineTaggedUnion({\n Disconnected: {},\n Connecting: {},\n Connected: {},\n Error: { error: Schema.String },\n})\nexport type SocketView = typeof SocketView.Type\n\n/**\n * Whether the socket is usable: open with no error outstanding. Gates sends\n * and received frames.\n */\nexport const isOpen = (model: WebSocketModel): boolean =>\n model.status === 'open' && model.lastError === null\n\n/**\n * The view of a socket, and whether its page still wants it. An error stands\n * until the page stops wanting the socket; a retry still waiting for its\n * socket reads as connecting, so it answers at once. An error on an open\n * socket always reads as an error.\n */\nexport const viewOf = (model: WebSocketModel, wanted: boolean): SocketView => {\n if (model.lastError !== null)\n return model.status === 'open' || !wanted\n ? SocketView.Error({ error: model.lastError })\n : SocketView.Connecting()\n if (model.status === 'open') return SocketView.Connected()\n if (model.status === 'connecting' || wanted) return SocketView.Connecting()\n return SocketView.Disconnected()\n}\n\n/** The live connection, for `send` and the incoming stream. One assembly holds one. */\nexport const Socket = ManagedResource.tag<Acquired>()('websocket')\n\n/** The socket service a placed WebSocket's streams and send commands require. */\nexport type SocketService = ManagedResource.ServiceOf<typeof Socket>\n\ntype SocketResources = Readonly<{\n socket: ManagedResource.Entry<\n WebSocketModel,\n WebSocketMessage,\n Option.Option<string>,\n Acquired,\n SocketService,\n () => WebSocketMessage\n >\n}>\n\ntype SendHelpers = Readonly<{\n send: (\n model: WebSocketModel,\n data: string,\n ) => Update.ReturnWithOutMessage<WebSocketModel, WebSocketMessage, never, SocketService>\n}>\n\nconst textOf = (data: unknown): Effect.Effect<string> =>\n typeof data === 'string'\n ? Effect.succeed(data)\n : data instanceof Blob\n ? Effect.promise(() => data.text())\n : data instanceof ArrayBuffer || ArrayBuffer.isView(data)\n ? Effect.succeed(new TextDecoder().decode(data))\n : Effect.succeed(String(data))\n\n/** Closes the socket with its handlers detached first, so the close reports nothing. */\nconst closeSilently = (socket: SocketHandle): void => {\n socket.onopen = null\n socket.onmessage = null\n socket.onclose = null\n socket.onerror = null\n socket.close()\n}\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nexport const websocket = <const Name extends string>(config: {\n readonly name: Name\n /** Defaults to the platform WebSocket, read lazily so tests can substitute a double. */\n readonly createSocket?: ((url: string) => SocketHandle) | undefined\n}) => {\n const createSocket: (url: string) => SocketHandle =\n config.createSocket ?? (url => new WebSocket(url) as unknown as SocketHandle)\n return Bundle.make<\n Name,\n WebSocketModel,\n WebSocketMessage,\n { readonly url: string; readonly connectTimeoutMs?: number | undefined },\n never,\n SocketService,\n SocketService,\n void,\n SocketResources,\n SendHelpers\n >(config.name, {\n Model: WebSocketModel,\n Message: WebSocketMessage,\n args: Schema.Struct({\n url: Schema.String,\n /** How long a socket may stay connecting before it is closed as `TimedOut`; unbounded when absent. */\n connectTimeoutMs: Schema.optional(\n Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())),\n ),\n }),\n init: (args): Update.Return<WebSocketModel, WebSocketMessage, SocketService> => ({\n model: { url: args.url, status: 'closed', lastError: null, opened: false },\n }),\n update: (\n model,\n message,\n ): Update.ReturnWithOutMessage<WebSocketModel, WebSocketMessage, never, SocketService> =>\n WebSocketMessage.match(message, {\n Connecting: () => ({\n model: unlessSame(model, {\n ...model,\n status: 'connecting',\n lastError: null,\n opened: false,\n }),\n }),\n Opened: () => ({\n model: unlessSame(model, { ...model, status: 'open', lastError: null, opened: true }),\n }),\n Received: () => ({ model }),\n // The close that follows an error, and the release after it, find it closed.\n Closed: () => ({ model: unlessSame(model, { ...model, status: 'closed' }) }),\n // Worded by whether the socket had opened: the socket's own text is\n // dropped in favor of the sentence, as a page would word it.\n Failed: () => ({\n model: unlessSame(model, {\n ...model,\n status: 'closed',\n lastError:\n model.status === 'open' ? 'Connection error' : 'Failed to connect to WebSocket',\n }),\n }),\n SendFailed: () => ({\n model: unlessSame(model, { ...model, lastError: 'Socket unavailable' }),\n }),\n Sent: () => ({ model }),\n TimedOut: () => ({\n model: unlessSame(model, {\n ...model,\n status: 'closed',\n lastError: 'Connection timeout',\n }),\n }),\n }),\n resources: () =>\n ManagedResource.make<WebSocketModel, WebSocketMessage>()(entry => ({\n socket: entry(Schema.Option(Schema.String), {\n resource: Socket,\n modelToMaybeRequirements: model => Option.some(model.url),\n acquire: (url: string) =>\n Effect.gen(function* () {\n const events = yield* Queue.unbounded<WebSocketMessage>()\n const socket = createSocket(url)\n // Offers run in arrival order: an async decode (Blob text) must\n // not overtake a later message. Rejections settle the chain\n // without breaking it; shutdown races are expected at teardown.\n let tail: Promise<void> = Promise.resolve()\n const enqueue = (effect: Effect.Effect<unknown>): void => {\n const run = () =>\n Effect.runPromise(effect).then(\n () => undefined,\n () => undefined,\n )\n tail = tail.then(run, run)\n }\n const offer = (message: WebSocketMessage) => enqueue(Queue.offer(events, message))\n socket.onopen = () => offer(WebSocketMessage.Opened())\n socket.onmessage = event => {\n enqueue(\n Effect.flatMap(textOf(event.data), text =>\n Queue.offer(events, WebSocketMessage.Received({ data: text })),\n ),\n )\n }\n socket.onclose = () => offer(WebSocketMessage.Closed())\n socket.onerror = () =>\n offer(WebSocketMessage.Failed({ message: `websocket error for ${url}` }))\n return { socket, events }\n }),\n onAcquired: () => WebSocketMessage.Connecting(),\n onReleased: () => WebSocketMessage.Closed(),\n onAcquireError: error => WebSocketMessage.Failed({ message: failMessage(error) }),\n // Daemon teardown: close the socket and drain the queue.\n release: ({ socket, events }) =>\n Effect.asVoid(\n Effect.andThen(\n Effect.sync(() => closeSilently(socket)),\n Queue.shutdown(events),\n ),\n ),\n }),\n })),\n subscriptions: ({\n connectTimeoutMs,\n }): Subscription.Subscriptions<WebSocketModel, WebSocketMessage, SocketService> =>\n Subscription.make<WebSocketModel, WebSocketMessage, SocketService>()(entry => ({\n connectTimeout: entry(\n { connecting: Schema.Boolean },\n {\n modelToDependencies: model => ({ connecting: model.status === 'connecting' }),\n dependenciesToStream: ({ connecting }) =>\n !connecting || connectTimeoutMs === undefined\n ? Stream.empty\n : Stream.unwrap(\n Effect.matchEffect(Effect.andThen(Effect.sleep(connectTimeoutMs), Socket.get), {\n // Released while waiting: onReleased reports Closed.\n onFailure: () => Effect.succeed(Stream.empty),\n onSuccess: ({ socket }) =>\n Effect.sync((): Stream.Stream<WebSocketMessage> => {\n // 0 is WebSocket.CONNECTING. An open that beat the\n // timer wins; the handlers go before the close, so\n // the error and close it causes report nothing.\n if (socket.readyState !== 0) return Stream.empty\n closeSilently(socket)\n return Stream.make(WebSocketMessage.TimedOut())\n }),\n }),\n ),\n },\n ),\n incoming: entry(\n // Connecting to open is no reason to restart: the stream reads the\n // same queue, and replays an open it attached too late to see.\n { live: Schema.Boolean },\n {\n modelToDependencies: model => ({ live: model.status !== 'closed' }),\n dependenciesToStream: ({ live }) =>\n !live\n ? Stream.empty\n : Stream.unwrap(\n Effect.matchEffect(Socket.get, {\n // Released between the dependency read and attach: end\n // quietly; onReleased reports Closed through update.\n onFailure: () => Effect.succeed(Stream.empty),\n onSuccess: ({ socket, events }) =>\n Effect.succeed(\n Stream.concat(\n // A fast server opens before the stream attaches; without\n // this the status would stick at connecting while\n // messages flow. 1 is WebSocket.OPEN everywhere.\n socket.readyState === 1\n ? Stream.make(WebSocketMessage.Opened())\n : Stream.empty,\n Stream.fromQueue(events),\n ),\n ),\n }),\n ),\n },\n ),\n })),\n helpers: {\n send: (model, data) => ({\n model,\n commands: [\n {\n name: 'WebSocket.send',\n args: { data },\n effect: Effect.matchEffect(\n Effect.flatMap(Socket.get, ({ socket }) =>\n // A closed socket's send is a silent no-op per spec (only a\n // connecting socket throws), so check first: SendFailed either way.\n socket.readyState === 1\n ? Effect.map(\n Effect.sync(() => {\n socket.send(data)\n }),\n () => WebSocketMessage.Sent({ data }),\n )\n : Effect.fail(\n new Error(\n `cannot send: socket is not open (readyState ${socket.readyState})`,\n ),\n ),\n ),\n {\n onFailure: error =>\n Effect.succeed(WebSocketMessage.SendFailed({ message: failMessage(error) })),\n onSuccess: message => Effect.succeed(message),\n },\n ),\n },\n ],\n }),\n },\n })\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foldkit-primitives",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Ready-made primitives for the Elm architecture, packaged with foldkit-bundle: media queries, presence, timers, pagination, history, sockets, observers, clipboard, and interaction Bundles with their Behaviors.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"foldkit",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"peerDependencies": {
|
|
95
95
|
"effect": "^4.0.0",
|
|
96
96
|
"foldkit": "^0.165.0",
|
|
97
|
-
"foldkit-mixins": "^0.
|
|
97
|
+
"foldkit-mixins": "^0.7.0"
|
|
98
98
|
},
|
|
99
99
|
"devDependencies": {
|
|
100
100
|
"@effect/platform-browser": "4.0.0",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"tsdown": "^0.23.0",
|
|
106
106
|
"typescript": "5.9.3",
|
|
107
107
|
"ws": "^8.18.0",
|
|
108
|
-
"foldkit-mixins": "0.
|
|
108
|
+
"foldkit-mixins": "0.7.0"
|
|
109
109
|
},
|
|
110
110
|
"peerDependenciesMeta": {
|
|
111
111
|
"foldkit-mixins": {
|