001/*
002 * Copyright 2022-2026 Revetware LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.soklet;
018
019import org.jspecify.annotations.NonNull;
020
021import java.util.function.Function;
022
023/**
024 * Broadcasts a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events">Server-Sent Event</a> payload to all clients listening on a {@link ResourcePath}.
025 * <p>
026 * For example:
027 * <pre>{@code // Acquire our SSE broadcaster (sends to anyone listening to "/examples/123")
028 * ServerSentEventServer server = ...;
029 * ServerSentEventBroadcaster broadcaster = server.acquireBroadcaster(ResourcePath.fromPath("/examples/123")).orElseThrow();
030 *
031 * // Create our SSE payload
032 * ServerSentEvent event = ServerSentEvent.withEvent("test")
033 *   .data("example")
034 *   .build();
035 *
036 * // Publish SSE payload to all listening clients
037 * broadcaster.broadcastEvent(event);}</pre>
038 * <p>
039 * Soklet's default {@link ServerSentEventServer} implementation guarantees at most one broadcaster is registered per {@link ResourcePath} at a time; instances may be recreated after becoming idle. That implementation is responsible for the creation and management of {@link ServerSentEventBroadcaster} instances.
040 * <p>
041 * You may acquire a broadcaster via {@link ServerSentEventServer#acquireBroadcaster(ResourcePath)}.
042 * <p>
043 * See <a href="https://www.soklet.com/docs/server-sent-events">https://www.soklet.com/docs/server-sent-events</a> for detailed documentation.
044 * <p>
045 * Formal specification is available at <a href="https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events">https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events</a>.
046 *
047 * @author <a href="https://www.revetkn.com">Mark Allen</a>
048 */
049public interface ServerSentEventBroadcaster {
050        /**
051         * The runtime Resource Path with which this broadcaster is associated.
052         * <p>
053         * Soklet guarantees exactly one {@link ServerSentEventBroadcaster} instance exists per {@link ResourcePath}.
054         * <p>
055         * For example, a client may register for SSE broadcasts for <em>Resource Method</em> {@code @ServerSentEventSource("/examples/{exampleId}")} by making a request to {@code GET /examples/123}.
056         * <p>
057         * A broadcaster specific to {@code /examples/123} is then created (if necessary) and managed by Soklet, and can be used to send SSE payloads to all clients via {@link #broadcastEvent(ServerSentEvent)}.
058         *
059         * @return the runtime Resource Path instance with which this broadcaster is associated
060         */
061        @NonNull
062        ResourcePath getResourcePath();
063
064        /**
065         * Approximately how many clients are listening to this broadcaster's {@link ResourcePath}?
066         * <p>
067         * For performance reasons, this number may be an estimate, or a snapshot of a recent moment-in-time.
068         * It's possible for some clients to have already disconnected, but we won't know until we attempt to broadcast to them.
069         *
070         * @return the approximate number of clients who will receive a broadcasted event
071         */
072        @NonNull
073        Long getClientCount();
074
075        /**
076         * Broadcasts a Server-Sent Event payload to all clients listening to this broadcaster's {@link ResourcePath}.
077         * <p>
078         * In practice, implementations will generally return "immediately" and broadcast operation[s] will occur on separate threads of execution.
079         * <p>
080         * However, mock implementations may wish to block until broadcasts have completed - for example, to simplify automated testing.
081         *
082         * @param serverSentEvent the Server-Sent Event payload to broadcast
083         */
084        void broadcastEvent(@NonNull ServerSentEvent serverSentEvent);
085
086        /**
087         * Broadcasts a Server-Sent Event where the payload is dynamically generated and memoized based on a specific trait of the client (e.g. {@link java.util.Locale} or User Role).
088         * <p>
089         * This method is designed for high-scale scenarios where generating the payload is expensive (e.g. JSON serialization with localization) and the number of distinct variations (keys) is significantly smaller than the number of clients.
090         * <p>
091         * The implementation guarantees that {@code eventProvider} is called exactly once per unique key derived by {@code keySelector} among the currently active clients.
092         * <p>
093         * In practice, implementations will generally return "immediately" and broadcast operation[s] will occur on separate threads of execution.
094         * <p>
095         * However, mock implementations may wish to block until broadcasts have completed - for example, to simplify automated testing.
096         *
097         * @param <T>           the type of the grouping key (e.g. {@link java.util.Locale} or {@link String})
098         * @param keySelector   a function that derives a grouping key from the client's associated context object.
099         *                      (If the client has no context, the implementation passes {@code null})
100         * @param eventProvider a function that provides the {@link ServerSentEvent} for a given key
101         */
102        <T> void broadcastEvent(@NonNull Function<Object, T> keySelector,
103                                                                                                        @NonNull Function<T, ServerSentEvent> eventProvider);
104
105        /**
106         * Broadcasts a single Server-Sent Event comment to all clients listening to this broadcaster's {@link ResourcePath}.
107         * <p>
108         * Use {@link ServerSentEventComment#heartbeatInstance()} to emit a heartbeat comment.
109         * <p>
110         * In practice, implementations will generally return "immediately" and broadcast operation[s] will occur on separate threads of execution.
111         * <p>
112         * However, mock implementations may wish to block until broadcasts have completed - for example, to simplify automated testing.
113         *
114         * @param serverSentEventComment the comment payload to broadcast
115         */
116        void broadcastComment(@NonNull ServerSentEventComment serverSentEventComment);
117
118        /**
119         * Broadcasts a Server-Sent Event comment where the payload is dynamically generated and memoized based on a specific trait of the client (e.g. {@link java.util.Locale} or User Role).
120         * <p>
121         * This follows the same memoization pattern as {@link #broadcastEvent(Function, Function)}.
122         * <p>
123         * The implementation guarantees that {@code commentProvider} is called exactly once per unique key derived by {@code keySelector} among the currently active clients.
124         * <p>
125         * In practice, implementations will generally return "immediately" and broadcast operation[s] will occur on separate threads of execution.
126         * <p>
127         * However, mock implementations may wish to block until broadcasts have completed - for example, to simplify automated testing.
128         *
129         * @param <T>             the type of the grouping key
130         * @param keySelector     a function that derives a grouping key from the client's associated context object
131         * @param commentProvider a function that provides the comment payload for a given key
132         */
133        <T> void broadcastComment(@NonNull Function<Object, T> keySelector,
134                                                                                                                @NonNull Function<T, ServerSentEventComment> commentProvider);
135}