foldkit-primitives 0.1.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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +454 -0
  3. package/dist/device/index.d.mts +312 -0
  4. package/dist/device/index.d.mts.map +1 -0
  5. package/dist/device/index.mjs +490 -0
  6. package/dist/device/index.mjs.map +1 -0
  7. package/dist/dom/index.d.mts +73 -0
  8. package/dist/dom/index.d.mts.map +1 -0
  9. package/dist/dom/index.mjs +218 -0
  10. package/dist/dom/index.mjs.map +1 -0
  11. package/dist/events/index.d.mts +154 -0
  12. package/dist/events/index.d.mts.map +1 -0
  13. package/dist/events/index.mjs +287 -0
  14. package/dist/events/index.mjs.map +1 -0
  15. package/dist/index.d.mts +10 -0
  16. package/dist/index.mjs +10 -0
  17. package/dist/media/index.d.mts +79 -0
  18. package/dist/media/index.d.mts.map +1 -0
  19. package/dist/media/index.mjs +127 -0
  20. package/dist/media/index.mjs.map +1 -0
  21. package/dist/motion/index.d.mts +112 -0
  22. package/dist/motion/index.d.mts.map +1 -0
  23. package/dist/motion/index.mjs +208 -0
  24. package/dist/motion/index.mjs.map +1 -0
  25. package/dist/net/index.d.mts +199 -0
  26. package/dist/net/index.d.mts.map +1 -0
  27. package/dist/net/index.mjs +329 -0
  28. package/dist/net/index.mjs.map +1 -0
  29. package/dist/observers/index.d.mts +59 -0
  30. package/dist/observers/index.d.mts.map +1 -0
  31. package/dist/observers/index.mjs +156 -0
  32. package/dist/observers/index.mjs.map +1 -0
  33. package/dist/state/index.d.mts +342 -0
  34. package/dist/state/index.d.mts.map +1 -0
  35. package/dist/state/index.mjs +504 -0
  36. package/dist/state/index.mjs.map +1 -0
  37. package/dist/time/index.d.mts +129 -0
  38. package/dist/time/index.d.mts.map +1 -0
  39. package/dist/time/index.mjs +213 -0
  40. package/dist/time/index.mjs.map +1 -0
  41. package/package.json +96 -0
@@ -0,0 +1,329 @@
1
+ import { Effect, Option, Queue, Schema, Stream } from "effect";
2
+ import { defineMessageUnion } from "foldkit/message";
3
+ import * as Subscription from "foldkit/subscription";
4
+ import { Bundle } from "foldkit-bundle";
5
+ import * as ManagedResource from "foldkit/managedResource";
6
+ //#region src/net/broadcast.ts
7
+ /**
8
+ * Cross-tab messaging as an entry plus a Command — not a bundle, because the
9
+ * channel owns no state: the parent maps `Received` into its own Message and
10
+ * stores whatever it keeps. One assembly may open the same name twice (two
11
+ * listeners); share the entry instead. By spec a post never echoes to its
12
+ * own channel, so a tab never hears itself.
13
+ */
14
+ const BroadcastMessage = defineMessageUnion({
15
+ /** Notifies without storing: project the payload into your own field to keep it. */
16
+ Received: { data: Schema.Unknown },
17
+ Posted: {},
18
+ BroadcastFailed: { message: Schema.String }
19
+ });
20
+ const failMessage$2 = (error) => error instanceof Error ? error.message : String(error);
21
+ const createChannel = (factory, name) => {
22
+ if (factory !== void 0) return factory(name);
23
+ if (typeof BroadcastChannel === "undefined") return null;
24
+ return new BroadcastChannel(name);
25
+ };
26
+ /**
27
+ * Every post to `name` from another channel, as Messages. Without a
28
+ * BroadcastChannel API the stream is empty instead of throwing; closing the
29
+ * stream closes the channel.
30
+ */
31
+ const broadcastMessages = (name, create) => Stream.unwrap(Effect.gen(function* () {
32
+ const channel = createChannel(create, name);
33
+ if (channel === null) return Stream.empty;
34
+ const queue = yield* Queue.unbounded();
35
+ channel.onmessage = (event) => {
36
+ Effect.runFork(Queue.offer(queue, BroadcastMessage.Received({ data: event.data })));
37
+ };
38
+ yield* Effect.addFinalizer(() => Effect.sync(() => {
39
+ channel.onmessage = null;
40
+ channel.close();
41
+ }));
42
+ return Stream.fromQueue(queue);
43
+ }));
44
+ /**
45
+ * Posts one message to `name`. Opens a channel, posts, and closes it: the
46
+ * send is one-shot, so it is a Command, not a resource. Without a
47
+ * BroadcastChannel API — or when the post throws — it yields
48
+ * `BroadcastFailed` instead of throwing.
49
+ */
50
+ const postBroadcast = (name, data, create) => ({
51
+ name: "Broadcast.post",
52
+ args: {
53
+ name,
54
+ data
55
+ },
56
+ effect: Effect.matchEffect(Effect.try({
57
+ try: () => {
58
+ const channel = createChannel(create, name);
59
+ if (channel === null) throw new Error("broadcast is unavailable");
60
+ try {
61
+ channel.postMessage(data);
62
+ } finally {
63
+ channel.close();
64
+ }
65
+ },
66
+ catch: failMessage$2
67
+ }), {
68
+ onFailure: (message) => Effect.succeed(BroadcastMessage.BroadcastFailed({ message })),
69
+ onSuccess: () => Effect.succeed(BroadcastMessage.Posted())
70
+ })
71
+ });
72
+ //#endregion
73
+ //#region src/net/online.ts
74
+ /**
75
+ * Online presence as a bundle: `online` in the Model, kept current by the
76
+ * window's online/offline events. The browser reports facts; the Model owns
77
+ * the state, so presence replays like any other transition.
78
+ */
79
+ const OnlineModel = Schema.Struct({ online: Schema.Boolean });
80
+ const OnlineMessage = defineMessageUnion({ Changed: { online: Schema.Boolean } });
81
+ const presenceStream = () => {
82
+ if (typeof window === "undefined") return Stream.empty;
83
+ return Stream.mergeAll([Stream.fromEventListener(window, "online").pipe(Stream.map(() => OnlineMessage.Changed({ online: true }))), Stream.fromEventListener(window, "offline").pipe(Stream.map(() => OnlineMessage.Changed({ online: false })))], { concurrency: "unbounded" });
84
+ };
85
+ const Online = Bundle.make("Online", {
86
+ Model: OnlineModel,
87
+ Message: OnlineMessage,
88
+ init: () => ({ model: { online: typeof navigator !== "undefined" && typeof navigator.onLine === "boolean" ? navigator.onLine : true } }),
89
+ update: (model, message) => ({ model: { online: message.online } }),
90
+ subscriptions: () => Subscription.make()(() => ({ changes: Subscription.persistent(presenceStream()) }))
91
+ });
92
+ //#endregion
93
+ //#region src/net/sse.ts
94
+ /**
95
+ * Server-sent events as a bundle: connection lifecycle in the Model, the
96
+ * event stream through one Managed Resource. One-directional, so there is no
97
+ * send: the server speaks, the Model listens. The browser reconnects dropped
98
+ * streams itself; a Failed only records the error.
99
+ */
100
+ const SseModel = Schema.Struct({
101
+ url: Schema.String,
102
+ status: Schema.Literals([
103
+ "closed",
104
+ "connecting",
105
+ "open"
106
+ ]),
107
+ lastError: Schema.NullOr(Schema.String)
108
+ });
109
+ const SseMessage = defineMessageUnion({
110
+ Connecting: {},
111
+ Opened: {},
112
+ /** Notifies without storing: project the payload into your own field to keep it. */
113
+ Received: { data: Schema.String },
114
+ Closed: {},
115
+ Failed: { message: Schema.String }
116
+ });
117
+ /** The live stream, for the incoming subscription. One assembly holds one. */
118
+ const Source = ManagedResource.tag()("sse");
119
+ const failMessage$1 = (error) => error instanceof Error ? error.message : String(error);
120
+ const sse = (config) => {
121
+ const createSource = config.createSource ?? ((url) => new EventSource(url));
122
+ return Bundle.make(config.name, {
123
+ Model: SseModel,
124
+ Message: SseMessage,
125
+ args: Schema.Struct({ url: Schema.String }),
126
+ init: (args) => ({ model: {
127
+ url: args.url,
128
+ status: "closed",
129
+ lastError: null
130
+ } }),
131
+ update: (model, message) => SseMessage.match(message, {
132
+ Connecting: () => ({ model: {
133
+ ...model,
134
+ status: "connecting"
135
+ } }),
136
+ Opened: () => ({ model: {
137
+ ...model,
138
+ status: "open",
139
+ lastError: null
140
+ } }),
141
+ Received: () => ({ model }),
142
+ Closed: () => ({ model: {
143
+ ...model,
144
+ status: "closed"
145
+ } }),
146
+ Failed: ({ message }) => ({ model: {
147
+ ...model,
148
+ status: "connecting",
149
+ lastError: message
150
+ } })
151
+ }),
152
+ resources: () => ManagedResource.make()((entry) => ({ source: entry(Schema.Option(Schema.String), {
153
+ resource: Source,
154
+ modelToMaybeRequirements: (model) => Option.some(model.url),
155
+ acquire: (url) => Effect.gen(function* () {
156
+ const events = yield* Queue.unbounded();
157
+ const source = createSource(url);
158
+ let tail = Promise.resolve();
159
+ const enqueue = (effect) => {
160
+ const run = () => Effect.runPromise(effect).then(() => void 0, () => void 0);
161
+ tail = tail.then(run, run);
162
+ };
163
+ const offer = (message) => enqueue(Queue.offer(events, message));
164
+ source.onopen = () => offer(SseMessage.Opened());
165
+ source.onmessage = (event) => offer(SseMessage.Received({ data: String(event.data) }));
166
+ source.onerror = () => offer(SseMessage.Failed({ message: `event stream error for ${url}` }));
167
+ return {
168
+ source,
169
+ events
170
+ };
171
+ }),
172
+ onAcquired: () => SseMessage.Connecting(),
173
+ onReleased: () => SseMessage.Closed(),
174
+ onAcquireError: (error) => SseMessage.Failed({ message: failMessage$1(error) }),
175
+ release: ({ source, events }) => Effect.asVoid(Effect.andThen(Effect.sync(() => {
176
+ source.onopen = null;
177
+ source.onmessage = null;
178
+ source.onerror = null;
179
+ source.close();
180
+ }), Queue.shutdown(events)))
181
+ }) })),
182
+ subscriptions: () => Subscription.make()((entry) => ({ incoming: entry({ status: Schema.Literals([
183
+ "closed",
184
+ "connecting",
185
+ "open"
186
+ ]) }, {
187
+ modelToDependencies: (model) => ({ status: model.status }),
188
+ dependenciesToStream: ({ status }) => status === "closed" ? Stream.empty : Stream.unwrap(Effect.matchEffect(Source.get, {
189
+ onFailure: () => Effect.succeed(Stream.empty),
190
+ onSuccess: ({ source, events }) => Effect.succeed(Stream.concat(source.readyState === 1 ? Stream.make(SseMessage.Opened()) : Stream.empty, Stream.fromQueue(events)))
191
+ }))
192
+ }) }))
193
+ });
194
+ };
195
+ //#endregion
196
+ //#region src/net/websocket.ts
197
+ /**
198
+ * A WebSocket as a bundle: connection lifecycle in the Model, full duplex
199
+ * through one Managed Resource. The resource owns the socket; a `send`
200
+ * helper writes through its tag service (lifted to the parent like every
201
+ * helper); a subscription stream reads incoming messages through the same
202
+ * tag. No hidden hub: everything the bundle touches is the Model, the
203
+ * resource registry, or a Message.
204
+ */
205
+ const WebSocketModel = Schema.Struct({
206
+ url: Schema.String,
207
+ status: Schema.Literals([
208
+ "closed",
209
+ "connecting",
210
+ "open"
211
+ ]),
212
+ lastError: Schema.NullOr(Schema.String)
213
+ });
214
+ const WebSocketMessage = defineMessageUnion({
215
+ Connecting: {},
216
+ Opened: {},
217
+ /** Notifies without storing: project the payload into your own field to keep it. */
218
+ Received: { data: Schema.String },
219
+ Closed: {},
220
+ Failed: { message: Schema.String },
221
+ SendFailed: { message: Schema.String },
222
+ /** Acknowledges dispatch, not delivery; update ignores it. */
223
+ Sent: {}
224
+ });
225
+ /** The live connection, for `send` and the incoming stream. One assembly holds one. */
226
+ const Socket = ManagedResource.tag()("websocket");
227
+ const textOf = (data) => typeof data === "string" ? Effect.succeed(data) : data instanceof Blob ? Effect.promise(() => data.text()) : data instanceof ArrayBuffer || ArrayBuffer.isView(data) ? Effect.succeed(new TextDecoder().decode(data)) : Effect.succeed(String(data));
228
+ const failMessage = (error) => error instanceof Error ? error.message : String(error);
229
+ const websocket = (config) => {
230
+ const createSocket = config.createSocket ?? ((url) => new WebSocket(url));
231
+ return Bundle.make(config.name, {
232
+ Model: WebSocketModel,
233
+ Message: WebSocketMessage,
234
+ args: Schema.Struct({ url: Schema.String }),
235
+ init: (args) => ({ model: {
236
+ url: args.url,
237
+ status: "closed",
238
+ lastError: null
239
+ } }),
240
+ update: (model, message) => WebSocketMessage.match(message, {
241
+ Connecting: () => ({ model: {
242
+ ...model,
243
+ status: "connecting"
244
+ } }),
245
+ Opened: () => ({ model: {
246
+ ...model,
247
+ status: "open",
248
+ lastError: null
249
+ } }),
250
+ Received: () => ({ model }),
251
+ Closed: () => ({ model: {
252
+ ...model,
253
+ status: "closed"
254
+ } }),
255
+ Failed: ({ message }) => ({ model: {
256
+ ...model,
257
+ status: "closed",
258
+ lastError: message
259
+ } }),
260
+ SendFailed: ({ message }) => ({ model: {
261
+ ...model,
262
+ lastError: message
263
+ } }),
264
+ Sent: () => ({ model })
265
+ }),
266
+ resources: () => ManagedResource.make()((entry) => ({ socket: entry(Schema.Option(Schema.String), {
267
+ resource: Socket,
268
+ modelToMaybeRequirements: (model) => Option.some(model.url),
269
+ acquire: (url) => Effect.gen(function* () {
270
+ const events = yield* Queue.unbounded();
271
+ const socket = createSocket(url);
272
+ let tail = Promise.resolve();
273
+ const enqueue = (effect) => {
274
+ const run = () => Effect.runPromise(effect).then(() => void 0, () => void 0);
275
+ tail = tail.then(run, run);
276
+ };
277
+ const offer = (message) => enqueue(Queue.offer(events, message));
278
+ socket.onopen = () => offer(WebSocketMessage.Opened());
279
+ socket.onmessage = (event) => {
280
+ enqueue(Effect.flatMap(textOf(event.data), (text) => Queue.offer(events, WebSocketMessage.Received({ data: text }))));
281
+ };
282
+ socket.onclose = () => offer(WebSocketMessage.Closed());
283
+ socket.onerror = () => offer(WebSocketMessage.Failed({ message: `websocket error for ${url}` }));
284
+ return {
285
+ socket,
286
+ events
287
+ };
288
+ }),
289
+ onAcquired: () => WebSocketMessage.Connecting(),
290
+ onReleased: () => WebSocketMessage.Closed(),
291
+ onAcquireError: (error) => WebSocketMessage.Failed({ message: failMessage(error) }),
292
+ release: ({ socket, events }) => Effect.asVoid(Effect.andThen(Effect.sync(() => {
293
+ socket.onopen = null;
294
+ socket.onmessage = null;
295
+ socket.onclose = null;
296
+ socket.onerror = null;
297
+ socket.close();
298
+ }), Queue.shutdown(events)))
299
+ }) })),
300
+ subscriptions: () => Subscription.make()((entry) => ({ incoming: entry({ status: Schema.Literals([
301
+ "closed",
302
+ "connecting",
303
+ "open"
304
+ ]) }, {
305
+ modelToDependencies: (model) => ({ status: model.status }),
306
+ dependenciesToStream: ({ status }) => status === "closed" ? Stream.empty : Stream.unwrap(Effect.matchEffect(Socket.get, {
307
+ onFailure: () => Effect.succeed(Stream.empty),
308
+ onSuccess: ({ socket, events }) => Effect.succeed(Stream.concat(socket.readyState === 1 ? Stream.make(WebSocketMessage.Opened()) : Stream.empty, Stream.fromQueue(events)))
309
+ }))
310
+ }) })),
311
+ helpers: { send: (model, data) => ({
312
+ model,
313
+ commands: [{
314
+ name: "WebSocket.send",
315
+ args: { data },
316
+ effect: Effect.matchEffect(Effect.flatMap(Socket.get, ({ socket }) => socket.readyState === 1 ? Effect.map(Effect.sync(() => {
317
+ socket.send(data);
318
+ }), () => WebSocketMessage.Sent()) : Effect.fail(/* @__PURE__ */ new Error(`cannot send: socket is not open (readyState ${socket.readyState})`))), {
319
+ onFailure: (error) => Effect.succeed(WebSocketMessage.SendFailed({ message: failMessage(error) })),
320
+ onSuccess: (message) => Effect.succeed(message)
321
+ })
322
+ }]
323
+ }) }
324
+ });
325
+ };
326
+ //#endregion
327
+ export { BroadcastMessage, Online, OnlineMessage, OnlineModel, Socket, Source, SseMessage, SseModel, WebSocketMessage, WebSocketModel, broadcastMessages, postBroadcast, sse, websocket };
328
+
329
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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) => ({ 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 * as ManagedResource from 'foldkit/managedResource'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\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/** 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: { ...model, status: 'connecting' as const } }),\n Opened: () => ({ model: { ...model, status: 'open' as const, lastError: null } }),\n Received: () => ({ model }),\n Closed: () => ({ model: { ...model, status: 'closed' as const } }),\n Failed: ({ message }) => ({\n model: { ...model, status: 'connecting' as const, 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 { status: Schema.Literals(['closed', 'connecting', 'open']) },\n {\n modelToDependencies: model => ({ status: model.status }),\n dependenciesToStream: ({ status }) =>\n status === 'closed'\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 * as ManagedResource from 'foldkit/managedResource'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\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})\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, not delivery; update ignores it. */\n Sent: {},\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/** 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\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 },\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({ url: Schema.String }),\n init: (args): Update.Return<WebSocketModel, WebSocketMessage, SocketService> => ({\n model: { url: args.url, status: 'closed', lastError: null },\n }),\n update: (\n model,\n message,\n ): Update.ReturnWithOutMessage<WebSocketModel, WebSocketMessage, never, SocketService> =>\n WebSocketMessage.match(message, {\n Connecting: () => ({ model: { ...model, status: 'connecting' as const } }),\n Opened: () => ({ model: { ...model, status: 'open' as const, lastError: null } }),\n Received: () => ({ model }),\n Closed: () => ({ model: { ...model, status: 'closed' as const } }),\n Failed: ({ message }) => ({\n model: { ...model, status: 'closed' as const, lastError: message },\n }),\n SendFailed: ({ message }) => ({ model: { ...model, lastError: message } }),\n Sent: () => ({ model }),\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(() => {\n socket.onopen = null\n socket.onmessage = null\n socket.onclose = null\n socket.onerror = null\n socket.close()\n }),\n Queue.shutdown(events),\n ),\n ),\n }),\n })),\n subscriptions: (): Subscription.Subscriptions<\n WebSocketModel,\n WebSocketMessage,\n SocketService\n > =>\n Subscription.make<WebSocketModel, WebSocketMessage, SocketService>()(entry => ({\n incoming: entry(\n { status: Schema.Literals(['closed', 'connecting', 'open']) },\n {\n modelToDependencies: model => ({ status: model.status }),\n dependenciesToStream: ({ status }) =>\n status === 'closed'\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(),\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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"}
@@ -0,0 +1,59 @@
1
+ import { Schema } from "effect";
2
+ import * as Mount from "foldkit/mount";
3
+ //#region src/observers/bounds.d.ts
4
+ export declare const Measured: Schema.TaggedStruct<"Measured", {
5
+ readonly x: Schema.Number;
6
+ readonly y: Schema.Number;
7
+ readonly width: Schema.Number;
8
+ readonly height: Schema.Number;
9
+ }>;
10
+ export type Measured = typeof Measured.Type;
11
+ export declare const Bounds: Mount.MountDefinitionNoArgs<"Bounds", {
12
+ readonly _tag: "Measured";
13
+ readonly x: number;
14
+ readonly y: number;
15
+ readonly width: number;
16
+ readonly height: number;
17
+ }>;
18
+ //#endregion
19
+ //#region src/observers/intersection.d.ts
20
+ export declare const IntersectionChanged: Schema.TaggedStruct<"IntersectionChanged", {
21
+ readonly isIntersecting: Schema.Boolean;
22
+ readonly ratio: Schema.Number;
23
+ }>;
24
+ export type IntersectionChanged = typeof IntersectionChanged.Type;
25
+ export declare const Intersection: Mount.MountDefinitionNoArgs<"Intersection", {
26
+ readonly _tag: "IntersectionChanged";
27
+ readonly isIntersecting: boolean;
28
+ readonly ratio: number;
29
+ }>;
30
+ //#endregion
31
+ //#region src/observers/mutation.d.ts
32
+ export declare const Mutated: Schema.TaggedStruct<"Mutated", {
33
+ readonly type: Schema.Literals<readonly ["childList", "attributes", "characterData"]>;
34
+ readonly added: Schema.$Array<Schema.String>;
35
+ readonly removed: Schema.$Array<Schema.String>;
36
+ readonly attribute: Schema.NullOr<Schema.String>;
37
+ }>;
38
+ export type Mutated = typeof Mutated.Type;
39
+ export declare const Mutation: Mount.MountDefinitionNoArgs<"Mutation", {
40
+ readonly _tag: "Mutated";
41
+ readonly type: "childList" | "attributes" | "characterData";
42
+ readonly added: readonly string[];
43
+ readonly removed: readonly string[];
44
+ readonly attribute: string | null;
45
+ }>;
46
+ //#endregion
47
+ //#region src/observers/resize.d.ts
48
+ export declare const Resized: Schema.TaggedStruct<"Resized", {
49
+ readonly width: Schema.Number;
50
+ readonly height: Schema.Number;
51
+ }>;
52
+ export type Resized = typeof Resized.Type;
53
+ export declare const Resize: Mount.MountDefinitionNoArgs<"Resize", {
54
+ readonly _tag: "Resized";
55
+ readonly width: number;
56
+ readonly height: number;
57
+ }>;
58
+ //#endregion
59
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/observers/bounds.ts","../../src/observers/intersection.ts","../../src/observers/mutation.ts","../../src/observers/resize.ts"],"mappings":";;;qBAUa,UAAQ,OAAA;;;;;;YAMT,kBAAkB,SAAS;qBAO1B,QAAM,MAAA;;;;;;;;;qBCfN,qBAAmB,OAAA;;;;YAIpB,6BAA6B,oBAAoB;qBAIhD,cAAY,MAAA;;;;;;;qBCLZ,SAAO,OAAA;;;;;;YAMR,iBAAiB,QAAQ;qBAOxB,UAAQ,MAAA;;;;;;;;;qBChBR,SAAO,OAAA;;;;YAIR,iBAAiB,QAAQ;qBAIxB,QAAM,MAAA"}
@@ -0,0 +1,156 @@
1
+ import { Effect, Queue, Schema, Stream } from "effect";
2
+ import * as Mount from "foldkit/mount";
3
+ //#region src/observers/bounds.ts
4
+ /**
5
+ * Element bounds as a Mount: the bounding rect re-measured on
6
+ * ResizeObserver, window scroll, and window resize, starting with the
7
+ * current rect. Without a ResizeObserver the window events still measure —
8
+ * position changes never needed the observer. No window (SSR) emits nothing
9
+ * instead of throwing.
10
+ */
11
+ const Measured = Schema.TaggedStruct("Measured", {
12
+ x: Schema.Number,
13
+ y: Schema.Number,
14
+ width: Schema.Number,
15
+ height: Schema.Number
16
+ });
17
+ const Bounds = Mount.defineStream("Bounds", {
18
+ messages: [Measured],
19
+ execute: ({ element }) => Stream.callback((queue) => Effect.gen(function* () {
20
+ if (typeof window === "undefined") return;
21
+ const measure = () => {
22
+ const rect = element.getBoundingClientRect();
23
+ Queue.offerUnsafe(queue, Measured.make({
24
+ x: rect.x,
25
+ y: rect.y,
26
+ width: rect.width,
27
+ height: rect.height
28
+ }));
29
+ };
30
+ yield* Effect.acquireRelease(Effect.sync(() => {
31
+ window.addEventListener("scroll", measure, true);
32
+ window.addEventListener("resize", measure);
33
+ const Observed = globalThis.ResizeObserver;
34
+ if (Observed === void 0) return null;
35
+ const observer = new Observed(measure);
36
+ observer.observe(element);
37
+ return observer;
38
+ }), (observer) => Effect.sync(() => {
39
+ window.removeEventListener("scroll", measure, true);
40
+ window.removeEventListener("resize", measure);
41
+ observer?.disconnect();
42
+ }));
43
+ measure();
44
+ }))
45
+ });
46
+ //#endregion
47
+ //#region src/observers/intersection.ts
48
+ /**
49
+ * Element visibility as a Mount: IntersectionObserver reports viewport
50
+ * crossings as Messages for the element carrying this Mount. No observer
51
+ * (SSR, old browser) emits nothing instead of throwing.
52
+ */
53
+ const IntersectionChanged = Schema.TaggedStruct("IntersectionChanged", {
54
+ isIntersecting: Schema.Boolean,
55
+ ratio: Schema.Number
56
+ });
57
+ const Intersection = Mount.defineStream("Intersection", {
58
+ messages: [IntersectionChanged],
59
+ execute: ({ element }) => Stream.callback((queue) => Effect.gen(function* () {
60
+ const Observed = globalThis.IntersectionObserver;
61
+ if (Observed === void 0) return;
62
+ yield* Effect.acquireRelease(Effect.sync(() => {
63
+ const observer = new Observed((entries) => {
64
+ const first = entries[0];
65
+ if (first !== void 0) Queue.offerUnsafe(queue, IntersectionChanged.make({
66
+ isIntersecting: first.isIntersecting,
67
+ ratio: first.intersectionRatio
68
+ }));
69
+ });
70
+ observer.observe(element);
71
+ return observer;
72
+ }), (observer) => Effect.sync(() => observer.disconnect()));
73
+ }))
74
+ });
75
+ //#endregion
76
+ //#region src/observers/mutation.ts
77
+ /**
78
+ * DOM mutations as a Mount: MutationObserver reports child, attribute, and
79
+ * text changes as Messages for the element carrying this Mount. Nodes cross
80
+ * as their names (serializable); the live nodes stay in the DOM, never in
81
+ * the Model. No observer (SSR, old browser) emits nothing instead of
82
+ * throwing. Observes the whole subtree — narrow by placing on a smaller
83
+ * element, not by options.
84
+ */
85
+ const Mutated = Schema.TaggedStruct("Mutated", {
86
+ type: Schema.Literals([
87
+ "childList",
88
+ "attributes",
89
+ "characterData"
90
+ ]),
91
+ added: Schema.Array(Schema.String),
92
+ removed: Schema.Array(Schema.String),
93
+ attribute: Schema.NullOr(Schema.String)
94
+ });
95
+ const recordType = (type) => type === "childList" || type === "attributes" || type === "characterData" ? type : null;
96
+ const Mutation = Mount.defineStream("Mutation", {
97
+ messages: [Mutated],
98
+ execute: ({ element }) => Stream.callback((queue) => Effect.gen(function* () {
99
+ const Observed = globalThis.MutationObserver;
100
+ if (Observed === void 0) return;
101
+ yield* Effect.acquireRelease(Effect.sync(() => {
102
+ const observer = new Observed((records) => {
103
+ for (const record of records) {
104
+ const type = recordType(record.type);
105
+ if (type === null) continue;
106
+ Queue.offerUnsafe(queue, Mutated.make({
107
+ type,
108
+ added: type === "childList" ? Array.from(record.addedNodes, (node) => node.nodeName) : [],
109
+ removed: type === "childList" ? Array.from(record.removedNodes, (node) => node.nodeName) : [],
110
+ attribute: type === "attributes" ? record.attributeName ?? null : null
111
+ }));
112
+ }
113
+ });
114
+ observer.observe(element, {
115
+ childList: true,
116
+ attributes: true,
117
+ characterData: true,
118
+ subtree: true
119
+ });
120
+ return observer;
121
+ }), (observer) => Effect.sync(() => observer.disconnect()));
122
+ }))
123
+ });
124
+ //#endregion
125
+ //#region src/observers/resize.ts
126
+ /**
127
+ * Element size as a Mount: ResizeObserver reports content-box changes as
128
+ * Messages for the element carrying this Mount. No observer (SSR, old
129
+ * browser) emits nothing instead of throwing.
130
+ */
131
+ const Resized = Schema.TaggedStruct("Resized", {
132
+ width: Schema.Number,
133
+ height: Schema.Number
134
+ });
135
+ const Resize = Mount.defineStream("Resize", {
136
+ messages: [Resized],
137
+ execute: ({ element }) => Stream.callback((queue) => Effect.gen(function* () {
138
+ const Observed = globalThis.ResizeObserver;
139
+ if (Observed === void 0) return;
140
+ yield* Effect.acquireRelease(Effect.sync(() => {
141
+ const observer = new Observed((entries) => {
142
+ const rect = entries[0]?.contentRect;
143
+ if (rect !== void 0) Queue.offerUnsafe(queue, Resized.make({
144
+ width: rect.width,
145
+ height: rect.height
146
+ }));
147
+ });
148
+ observer.observe(element);
149
+ return observer;
150
+ }), (observer) => Effect.sync(() => observer.disconnect()));
151
+ }))
152
+ });
153
+ //#endregion
154
+ export { Bounds, Intersection, IntersectionChanged, Measured, Mutated, Mutation, Resize, Resized };
155
+
156
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/observers/bounds.ts","../../src/observers/intersection.ts","../../src/observers/mutation.ts","../../src/observers/resize.ts"],"sourcesContent":["/**\n * Element bounds as a Mount: the bounding rect re-measured on\n * ResizeObserver, window scroll, and window resize, starting with the\n * current rect. Without a ResizeObserver the window events still measure —\n * position changes never needed the observer. No window (SSR) emits nothing\n * instead of throwing.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport * as Mount from 'foldkit/mount'\n\nexport const Measured = Schema.TaggedStruct('Measured', {\n x: Schema.Number,\n y: Schema.Number,\n width: Schema.Number,\n height: Schema.Number,\n})\nexport type Measured = typeof Measured.Type\n\ntype ObserverCtor = new (callback: () => void) => {\n observe(element: Element): void\n disconnect(): void\n}\n\nexport const Bounds = Mount.defineStream('Bounds', {\n messages: [Measured],\n execute: ({ element }) =>\n Stream.callback<typeof Measured.Type>(queue =>\n Effect.gen(function* () {\n if (typeof window === 'undefined') return\n const measure = () => {\n const rect = element.getBoundingClientRect()\n Queue.offerUnsafe(\n queue,\n Measured.make({ x: rect.x, y: rect.y, width: rect.width, height: rect.height }),\n )\n }\n // Capture phase: container scrolls do not bubble, but every scroll\n // descends past the window first.\n yield* Effect.acquireRelease(\n Effect.sync(() => {\n window.addEventListener('scroll', measure, true)\n window.addEventListener('resize', measure)\n const Observed = (globalThis as { ResizeObserver?: ObserverCtor }).ResizeObserver\n if (Observed === undefined) return null\n const observer = new Observed(measure)\n observer.observe(element)\n return observer\n }),\n observer =>\n Effect.sync(() => {\n window.removeEventListener('scroll', measure, true)\n window.removeEventListener('resize', measure)\n observer?.disconnect()\n }),\n )\n measure()\n }),\n ),\n})\n","/**\n * Element visibility as a Mount: IntersectionObserver reports viewport\n * crossings as Messages for the element carrying this Mount. No observer\n * (SSR, old browser) emits nothing instead of throwing.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport * as Mount from 'foldkit/mount'\n\nexport const IntersectionChanged = Schema.TaggedStruct('IntersectionChanged', {\n isIntersecting: Schema.Boolean,\n ratio: Schema.Number,\n})\nexport type IntersectionChanged = typeof IntersectionChanged.Type\n\ntype ObserverCtor = new (callback: IntersectionObserverCallback) => IntersectionObserver\n\nexport const Intersection = Mount.defineStream('Intersection', {\n messages: [IntersectionChanged],\n execute: ({ element }) =>\n Stream.callback<typeof IntersectionChanged.Type>(queue =>\n Effect.gen(function* () {\n const Observed = (globalThis as { IntersectionObserver?: ObserverCtor })\n .IntersectionObserver\n if (Observed === undefined) return\n yield* Effect.acquireRelease(\n Effect.sync(() => {\n const observer = new Observed(entries => {\n const first = entries[0]\n if (first !== undefined) {\n Queue.offerUnsafe(\n queue,\n IntersectionChanged.make({\n isIntersecting: first.isIntersecting,\n ratio: first.intersectionRatio,\n }),\n )\n }\n })\n observer.observe(element)\n return observer\n }),\n observer => Effect.sync(() => observer.disconnect()),\n )\n }),\n ),\n})\n","/**\n * DOM mutations as a Mount: MutationObserver reports child, attribute, and\n * text changes as Messages for the element carrying this Mount. Nodes cross\n * as their names (serializable); the live nodes stay in the DOM, never in\n * the Model. No observer (SSR, old browser) emits nothing instead of\n * throwing. Observes the whole subtree — narrow by placing on a smaller\n * element, not by options.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport * as Mount from 'foldkit/mount'\n\nexport const Mutated = Schema.TaggedStruct('Mutated', {\n type: Schema.Literals(['childList', 'attributes', 'characterData']),\n added: Schema.Array(Schema.String),\n removed: Schema.Array(Schema.String),\n attribute: Schema.NullOr(Schema.String),\n})\nexport type Mutated = typeof Mutated.Type\n\ntype ObserverCtor = new (callback: MutationCallback) => MutationObserver\n\nconst recordType = (type: string): Mutated['type'] | null =>\n type === 'childList' || type === 'attributes' || type === 'characterData' ? type : null\n\nexport const Mutation = Mount.defineStream('Mutation', {\n messages: [Mutated],\n execute: ({ element }) =>\n Stream.callback<typeof Mutated.Type>(queue =>\n Effect.gen(function* () {\n const Observed = (globalThis as { MutationObserver?: ObserverCtor }).MutationObserver\n if (Observed === undefined) return\n yield* Effect.acquireRelease(\n Effect.sync(() => {\n const observer = new Observed(records => {\n for (const record of records) {\n const type = recordType(record.type)\n if (type === null) continue\n Queue.offerUnsafe(\n queue,\n Mutated.make({\n type,\n added:\n type === 'childList'\n ? Array.from(record.addedNodes, node => node.nodeName)\n : [],\n removed:\n type === 'childList'\n ? Array.from(record.removedNodes, node => node.nodeName)\n : [],\n attribute: type === 'attributes' ? (record.attributeName ?? null) : null,\n }),\n )\n }\n })\n observer.observe(element, {\n childList: true,\n attributes: true,\n characterData: true,\n subtree: true,\n })\n return observer\n }),\n observer => Effect.sync(() => observer.disconnect()),\n )\n }),\n ),\n})\n","/**\n * Element size as a Mount: ResizeObserver reports content-box changes as\n * Messages for the element carrying this Mount. No observer (SSR, old\n * browser) emits nothing instead of throwing.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport * as Mount from 'foldkit/mount'\n\nexport const Resized = Schema.TaggedStruct('Resized', {\n width: Schema.Number,\n height: Schema.Number,\n})\nexport type Resized = typeof Resized.Type\n\ntype ObserverCtor = new (callback: ResizeObserverCallback) => ResizeObserver\n\nexport const Resize = Mount.defineStream('Resize', {\n messages: [Resized],\n execute: ({ element }) =>\n Stream.callback<typeof Resized.Type>(queue =>\n Effect.gen(function* () {\n const Observed = (globalThis as { ResizeObserver?: ObserverCtor }).ResizeObserver\n if (Observed === undefined) return\n yield* Effect.acquireRelease(\n Effect.sync(() => {\n const observer = new Observed(entries => {\n const rect = entries[0]?.contentRect\n if (rect !== undefined) {\n Queue.offerUnsafe(queue, Resized.make({ width: rect.width, height: rect.height }))\n }\n })\n observer.observe(element)\n return observer\n }),\n observer => Effect.sync(() => observer.disconnect()),\n )\n }),\n ),\n})\n"],"mappings":"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"}