@orpc/publisher 1.14.11

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.
@@ -0,0 +1,125 @@
1
+ import { RPCSerializer } from '@orpc/client';
2
+ import { stringifyJSON, parseEmptyableJSON, once } from '@orpc/shared';
3
+ import { unwrapEvent, withEventMeta, getEventMeta } from '@standardserver/core';
4
+ import { P as Publisher } from '../shared/publisher.BSQf8ybR.mjs';
5
+
6
+ class RedisPublisher extends Publisher {
7
+ constructor(redis, options = {}) {
8
+ super(options);
9
+ this.redis = redis;
10
+ this.prefix = options.prefix ?? "";
11
+ this.serializer = options.serializer ?? new RPCSerializer();
12
+ this.resumeEnabled = options.resume?.enabled ?? false;
13
+ this.resumeSeconds = options.resume?.seconds ?? 300;
14
+ this.subscriber = options.subscriber ?? redis.duplicate();
15
+ }
16
+ subscriber;
17
+ prefix;
18
+ serializer;
19
+ resumeEnabled;
20
+ resumeSeconds;
21
+ /**
22
+ * The exactness of the `XTRIM` command.
23
+ * Used for testing purpose.
24
+ */
25
+ xTrimExactness = "~";
26
+ firstPublishTimeMap = /* @__PURE__ */ new Map();
27
+ async publish(event, payload) {
28
+ const redisKey = `${this.prefix}${event}`;
29
+ const data = this.serializePayload(payload);
30
+ let id;
31
+ if (!this.redis.isOpen) {
32
+ await this.redis.connect();
33
+ }
34
+ if (this.resumeEnabled) {
35
+ const now = Date.now();
36
+ for (const [event2, firstPublishTime] of this.firstPublishTimeMap) {
37
+ if (firstPublishTime + this.resumeSeconds * 1e3 < now) {
38
+ this.firstPublishTimeMap.delete(event2);
39
+ }
40
+ }
41
+ if (!this.firstPublishTimeMap.has(event)) {
42
+ this.firstPublishTimeMap.set(event, now);
43
+ const result = await this.redis.multi().xAdd(redisKey, "*", { data: stringifyJSON(data) }).xTrim(redisKey, "MINID", `${now - this.resumeSeconds * 1e3}-0`, { strategyModifier: this.xTrimExactness }).expire(redisKey, this.resumeSeconds * 2).exec();
44
+ id = result[0];
45
+ } else {
46
+ id = await this.redis.xAdd(redisKey, "*", { data: stringifyJSON(data) });
47
+ }
48
+ }
49
+ await this.redis.publish(redisKey, stringifyJSON({ data, id }));
50
+ }
51
+ async subscribeListener(event, originalListener, { lastEventId, onError } = {}) {
52
+ const redisKey = `${this.prefix}${event}`;
53
+ if (!this.subscriber.isOpen) {
54
+ await this.subscriber.connect();
55
+ }
56
+ let pendingPayloads = [];
57
+ const resumedIds = /* @__PURE__ */ new Set();
58
+ const deduplicatingListener = (payload) => {
59
+ if (pendingPayloads) {
60
+ pendingPayloads.push(payload);
61
+ return;
62
+ }
63
+ const id = getEventMeta(payload)?.id;
64
+ if (id !== void 0 && resumedIds.has(id)) {
65
+ return;
66
+ }
67
+ originalListener(payload);
68
+ };
69
+ const redisListener = (message) => {
70
+ try {
71
+ const { id, data } = parseEmptyableJSON(message);
72
+ const payload = this.deserializePayload(id, data);
73
+ deduplicatingListener(payload);
74
+ } catch (error) {
75
+ onError?.(error);
76
+ }
77
+ };
78
+ try {
79
+ const subscribePromise = this.subscriber.subscribe(redisKey, redisListener);
80
+ try {
81
+ if (this.resumeEnabled && lastEventId !== void 0) {
82
+ if (!this.redis.isOpen) {
83
+ await this.redis.connect();
84
+ }
85
+ const results = await this.redis.xRead({ key: redisKey, id: lastEventId });
86
+ if (results && results[0]) {
87
+ const { messages } = results[0];
88
+ for (const { id, message } of messages) {
89
+ const rawData = message.data;
90
+ const data = parseEmptyableJSON(rawData);
91
+ const payload = this.deserializePayload(id, data);
92
+ resumedIds.add(id);
93
+ originalListener(payload);
94
+ }
95
+ }
96
+ }
97
+ } finally {
98
+ const pending = pendingPayloads;
99
+ pendingPayloads = void 0;
100
+ for (const payload of pending) {
101
+ deduplicatingListener(payload);
102
+ }
103
+ }
104
+ await subscribePromise;
105
+ } catch (error) {
106
+ await this.subscriber.unsubscribe(redisKey, redisListener);
107
+ throw error;
108
+ }
109
+ return once(async () => {
110
+ await this.subscriber.unsubscribe(redisKey, redisListener);
111
+ });
112
+ }
113
+ serializePayload(payload) {
114
+ const [original, meta] = unwrapEvent(payload);
115
+ return { payload: this.serializer.serialize(original, { useFormDataForBlobFields: false }), meta };
116
+ }
117
+ deserializePayload(id, { payload, meta }) {
118
+ return withEventMeta(
119
+ this.serializer.deserialize(payload),
120
+ id === void 0 ? { ...meta } : { ...meta, id }
121
+ );
122
+ }
123
+ }
124
+
125
+ export { RedisPublisher };
@@ -0,0 +1,74 @@
1
+ import { Redis } from '@upstash/redis';
2
+ import { Publisher, PublisherOptions, PublisherSubscribeListenerOptions } from '../index.mjs';
3
+ import { RPCSerializer } from '@orpc/client';
4
+ import '@orpc/shared';
5
+
6
+ interface UpstashPublisherOptions extends PublisherOptions {
7
+ /**
8
+ * The prefix to use for Redis keys.
9
+ *
10
+ * @default ''
11
+ */
12
+ prefix?: string;
13
+ /**
14
+ * Serializer for serialize and deserialize payloads.
15
+ *
16
+ * @default RPCSerializer
17
+ */
18
+ serializer?: undefined | Pick<RPCSerializer, keyof RPCSerializer>;
19
+ /**
20
+ * Configuration for event resume support.
21
+ *
22
+ * When enabled, published events are temporarily stored so new
23
+ * subscribers can resume from a previous position using `lastEventId`.
24
+ *
25
+ * @default { enabled: false }
26
+ */
27
+ resume?: {
28
+ /**
29
+ * Whether event resume support is enabled.
30
+ *
31
+ * When enabled, published events are temporarily stored so new
32
+ * subscribers can resume from a previous position using `lastEventId`.
33
+ *
34
+ * @default false
35
+ */
36
+ enabled: boolean;
37
+ /**
38
+ * How long (in seconds) to retain events for resume.
39
+ *
40
+ * Expired events are cleaned up lazily for performance reasons, so
41
+ * some events may remain available slightly longer than this period.
42
+ *
43
+ * @default 300 (5 min)
44
+ */
45
+ seconds?: number;
46
+ };
47
+ }
48
+ declare class UpstashPublisher<T extends Record<string, object>> extends Publisher<T> {
49
+ private readonly redis;
50
+ private readonly prefix;
51
+ private readonly serializer;
52
+ private readonly listenersMap;
53
+ private readonly onErrorsMap;
54
+ private readonly subscriptionMap;
55
+ private readonly resumeEnabled;
56
+ private readonly resumeSeconds;
57
+ /**
58
+ * The exactness of the `XTRIM` command.
59
+ * Used for testing purpose.
60
+ */
61
+ private readonly xTrimExactness;
62
+ constructor(redis: Redis, { resume, prefix, serializer, ...options }?: UpstashPublisherOptions);
63
+ private readonly firstPublishTimeMap;
64
+ publish<K extends keyof T & string>(event: K, payload: T[K]): Promise<void>;
65
+ protected subscribeListener<K extends keyof T & string>(event: K, originalListener: (payload: T[K]) => void, { lastEventId, onError }?: PublisherSubscribeListenerOptions): Promise<() => Promise<void>>;
66
+ private readonly pendingSubscriptionsMap;
67
+ private subscribeEvent;
68
+ private unsubscribeEvent;
69
+ private serializePayload;
70
+ private deserializePayload;
71
+ }
72
+
73
+ export { UpstashPublisher };
74
+ export type { UpstashPublisherOptions };
@@ -0,0 +1,74 @@
1
+ import { Redis } from '@upstash/redis';
2
+ import { Publisher, PublisherOptions, PublisherSubscribeListenerOptions } from '../index.js';
3
+ import { RPCSerializer } from '@orpc/client';
4
+ import '@orpc/shared';
5
+
6
+ interface UpstashPublisherOptions extends PublisherOptions {
7
+ /**
8
+ * The prefix to use for Redis keys.
9
+ *
10
+ * @default ''
11
+ */
12
+ prefix?: string;
13
+ /**
14
+ * Serializer for serialize and deserialize payloads.
15
+ *
16
+ * @default RPCSerializer
17
+ */
18
+ serializer?: undefined | Pick<RPCSerializer, keyof RPCSerializer>;
19
+ /**
20
+ * Configuration for event resume support.
21
+ *
22
+ * When enabled, published events are temporarily stored so new
23
+ * subscribers can resume from a previous position using `lastEventId`.
24
+ *
25
+ * @default { enabled: false }
26
+ */
27
+ resume?: {
28
+ /**
29
+ * Whether event resume support is enabled.
30
+ *
31
+ * When enabled, published events are temporarily stored so new
32
+ * subscribers can resume from a previous position using `lastEventId`.
33
+ *
34
+ * @default false
35
+ */
36
+ enabled: boolean;
37
+ /**
38
+ * How long (in seconds) to retain events for resume.
39
+ *
40
+ * Expired events are cleaned up lazily for performance reasons, so
41
+ * some events may remain available slightly longer than this period.
42
+ *
43
+ * @default 300 (5 min)
44
+ */
45
+ seconds?: number;
46
+ };
47
+ }
48
+ declare class UpstashPublisher<T extends Record<string, object>> extends Publisher<T> {
49
+ private readonly redis;
50
+ private readonly prefix;
51
+ private readonly serializer;
52
+ private readonly listenersMap;
53
+ private readonly onErrorsMap;
54
+ private readonly subscriptionMap;
55
+ private readonly resumeEnabled;
56
+ private readonly resumeSeconds;
57
+ /**
58
+ * The exactness of the `XTRIM` command.
59
+ * Used for testing purpose.
60
+ */
61
+ private readonly xTrimExactness;
62
+ constructor(redis: Redis, { resume, prefix, serializer, ...options }?: UpstashPublisherOptions);
63
+ private readonly firstPublishTimeMap;
64
+ publish<K extends keyof T & string>(event: K, payload: T[K]): Promise<void>;
65
+ protected subscribeListener<K extends keyof T & string>(event: K, originalListener: (payload: T[K]) => void, { lastEventId, onError }?: PublisherSubscribeListenerOptions): Promise<() => Promise<void>>;
66
+ private readonly pendingSubscriptionsMap;
67
+ private subscribeEvent;
68
+ private unsubscribeEvent;
69
+ private serializePayload;
70
+ private deserializePayload;
71
+ }
72
+
73
+ export { UpstashPublisher };
74
+ export type { UpstashPublisherOptions };
@@ -0,0 +1,200 @@
1
+ import { RPCSerializer } from '@orpc/client';
2
+ import { once } from '@orpc/shared';
3
+ import { unwrapEvent, withEventMeta, getEventMeta } from '@standardserver/core';
4
+ import { P as Publisher } from '../shared/publisher.BSQf8ybR.mjs';
5
+
6
+ class UpstashPublisher extends Publisher {
7
+ constructor(redis, { resume, prefix, serializer, ...options } = {}) {
8
+ super(options);
9
+ this.redis = redis;
10
+ this.prefix = prefix ?? "";
11
+ this.resumeEnabled = resume?.enabled ?? false;
12
+ this.resumeSeconds = resume?.seconds ?? 300;
13
+ this.serializer = serializer ?? new RPCSerializer();
14
+ }
15
+ prefix;
16
+ serializer;
17
+ listenersMap = /* @__PURE__ */ new Map();
18
+ onErrorsMap = /* @__PURE__ */ new Map();
19
+ subscriptionMap = /* @__PURE__ */ new Map();
20
+ // Upstash subscription objects
21
+ resumeEnabled;
22
+ resumeSeconds;
23
+ /**
24
+ * The exactness of the `XTRIM` command.
25
+ * Used for testing purpose.
26
+ */
27
+ xTrimExactness = "~";
28
+ firstPublishTimeMap = /* @__PURE__ */ new Map();
29
+ async publish(event, payload) {
30
+ const redisKey = `${this.prefix}${event}`;
31
+ const data = this.serializePayload(payload);
32
+ let id;
33
+ if (this.resumeEnabled) {
34
+ const now = Date.now();
35
+ for (const [event2, firstPublishTime] of this.firstPublishTimeMap) {
36
+ if (firstPublishTime + this.resumeSeconds * 1e3 < now) {
37
+ this.firstPublishTimeMap.delete(event2);
38
+ }
39
+ }
40
+ if (!this.firstPublishTimeMap.has(event)) {
41
+ this.firstPublishTimeMap.set(event, now);
42
+ const results = await this.redis.multi().xadd(redisKey, "*", { data }).xtrim(redisKey, { strategy: "MINID", exactness: this.xTrimExactness, threshold: `${now - this.resumeSeconds * 1e3}-0` }).expire(redisKey, this.resumeSeconds * 2).exec();
43
+ id = results[0];
44
+ } else {
45
+ id = await this.redis.xadd(redisKey, "*", { data });
46
+ }
47
+ }
48
+ await this.redis.publish(redisKey, { id, data });
49
+ }
50
+ async subscribeListener(event, originalListener, { lastEventId, onError } = {}) {
51
+ const redisKey = `${this.prefix}${event}`;
52
+ let pendingPayloads = [];
53
+ const resumedIds = /* @__PURE__ */ new Set();
54
+ const deduplicatingListener = (payload) => {
55
+ if (pendingPayloads) {
56
+ pendingPayloads.push(payload);
57
+ return;
58
+ }
59
+ const id = getEventMeta(payload)?.id;
60
+ if (id !== void 0 && resumedIds.has(id)) {
61
+ return;
62
+ }
63
+ originalListener(payload);
64
+ };
65
+ let listeners = this.listenersMap.get(event);
66
+ if (!listeners) {
67
+ this.listenersMap.set(event, listeners = []);
68
+ }
69
+ listeners.push(deduplicatingListener);
70
+ try {
71
+ const subscribeEventPromise = this.subscribeEvent(event);
72
+ try {
73
+ if (this.resumeEnabled && lastEventId !== void 0) {
74
+ const results = await this.redis.xread(redisKey, lastEventId);
75
+ if (results && results[0]) {
76
+ const [_, items] = results[0];
77
+ for (const [id, fields] of items) {
78
+ const data = fields[1];
79
+ const payload = this.deserializePayload(id, data);
80
+ resumedIds.add(id);
81
+ originalListener(payload);
82
+ }
83
+ }
84
+ }
85
+ } finally {
86
+ const pending = pendingPayloads;
87
+ pendingPayloads = void 0;
88
+ for (const payload of pending) {
89
+ deduplicatingListener(payload);
90
+ }
91
+ }
92
+ await subscribeEventPromise;
93
+ } catch (error) {
94
+ listeners.splice(listeners.indexOf(deduplicatingListener), 1);
95
+ if (listeners.length === 0) {
96
+ this.listenersMap.delete(event);
97
+ }
98
+ await this.unsubscribeEvent(event);
99
+ throw error;
100
+ }
101
+ if (onError) {
102
+ let onErrors = this.onErrorsMap.get(event);
103
+ if (!onErrors) {
104
+ this.onErrorsMap.set(event, onErrors = []);
105
+ }
106
+ onErrors.push(onError);
107
+ }
108
+ return once(async () => {
109
+ listeners.splice(listeners.indexOf(deduplicatingListener), 1);
110
+ if (onError) {
111
+ const onErrors = this.onErrorsMap.get(event);
112
+ if (onErrors) {
113
+ onErrors.splice(onErrors.indexOf(onError), 1);
114
+ }
115
+ }
116
+ if (listeners.length === 0) {
117
+ this.listenersMap.delete(event);
118
+ this.onErrorsMap.delete(event);
119
+ }
120
+ await this.unsubscribeEvent(event);
121
+ });
122
+ }
123
+ pendingSubscriptionsMap = /* @__PURE__ */ new Map();
124
+ async subscribeEvent(event) {
125
+ const redisKey = `${this.prefix}${event}`;
126
+ const pending = this.pendingSubscriptionsMap.get(event);
127
+ if (pending) {
128
+ try {
129
+ await pending;
130
+ } catch {
131
+ }
132
+ }
133
+ if (this.subscriptionMap.has(event)) {
134
+ return;
135
+ }
136
+ const dispatchErrorForEvent = (error) => {
137
+ const onErrors = this.onErrorsMap.get(event);
138
+ onErrors?.forEach((onError) => onError(error));
139
+ };
140
+ const subscription = this.redis.subscribe(redisKey);
141
+ subscription.on("message", (message) => {
142
+ try {
143
+ const listeners = this.listenersMap.get(event);
144
+ if (listeners) {
145
+ const { id, data } = message.message;
146
+ const payload = this.deserializePayload(id, data);
147
+ listeners.forEach((listener) => listener(payload));
148
+ }
149
+ } catch (error) {
150
+ dispatchErrorForEvent(error);
151
+ }
152
+ });
153
+ let resolvePromise;
154
+ let rejectPromise;
155
+ const promise = new Promise((resolve, reject) => {
156
+ resolvePromise = resolve;
157
+ rejectPromise = reject;
158
+ });
159
+ subscription.on("error", (error) => {
160
+ rejectPromise(error);
161
+ dispatchErrorForEvent(error);
162
+ });
163
+ subscription.on("subscribe", () => {
164
+ resolvePromise();
165
+ });
166
+ try {
167
+ this.pendingSubscriptionsMap.set(event, promise);
168
+ await promise;
169
+ this.subscriptionMap.set(event, subscription);
170
+ } finally {
171
+ this.pendingSubscriptionsMap.delete(event);
172
+ }
173
+ }
174
+ async unsubscribeEvent(event) {
175
+ const pending = this.pendingSubscriptionsMap.get(event);
176
+ if (pending) {
177
+ try {
178
+ await pending;
179
+ } catch {
180
+ }
181
+ }
182
+ const subscription = this.subscriptionMap.get(event);
183
+ if (!this.listenersMap.has(event) && subscription) {
184
+ this.subscriptionMap.delete(event);
185
+ await subscription.unsubscribe();
186
+ }
187
+ }
188
+ serializePayload(payload) {
189
+ const [original, meta] = unwrapEvent(payload);
190
+ return { payload: this.serializer.serialize(original, { useFormDataForBlobFields: false }), meta };
191
+ }
192
+ deserializePayload(id, { payload, meta }) {
193
+ return withEventMeta(
194
+ this.serializer.deserialize(payload),
195
+ id === void 0 ? { ...meta } : { ...meta, id }
196
+ );
197
+ }
198
+ }
199
+
200
+ export { UpstashPublisher };
@@ -0,0 +1,85 @@
1
+ import { ThrowableError, AsyncIteratorClass } from '@orpc/shared';
2
+
3
+ interface PublisherOptions {
4
+ /**
5
+ * Maximum number of events to buffer for AsyncIteratorObject subscribers.
6
+ *
7
+ * If the buffer exceeds this limit, the oldest event is dropped.
8
+ * This prevents unbounded memory growth if consumers process events slowly.
9
+ *
10
+ * Set to:
11
+ * - `0`: Disable buffering. Events must be consumed before the next one arrives.
12
+ * - `1`: Only keep the latest event. Useful for real-time updates where only the most recent value matters.
13
+ * - `Infinity`: Keep all events. Ensures no data loss, but may lead to high memory usage.
14
+ *
15
+ * @default 100
16
+ */
17
+ maxBufferedEvents?: number;
18
+ }
19
+ interface PublisherSubscribeListenerOptions {
20
+ /**
21
+ * Resume from a specific event ID
22
+ */
23
+ lastEventId?: string | undefined;
24
+ /**
25
+ * Triggered when an error occur
26
+ */
27
+ onError?: (error: ThrowableError) => void;
28
+ }
29
+ interface PublisherSubscribeIteratorOptions extends Pick<PublisherSubscribeListenerOptions, 'lastEventId'>, Pick<PublisherOptions, 'maxBufferedEvents'> {
30
+ /**
31
+ * Abort signal, automatically unsubscribes on abort
32
+ */
33
+ signal?: AbortSignal | undefined | null;
34
+ }
35
+ declare abstract class Publisher<T extends Record<string, object>> {
36
+ private readonly maxBufferedEvents;
37
+ constructor(options?: PublisherOptions);
38
+ /**
39
+ * Publish an event to subscribers
40
+ */
41
+ abstract publish<K extends keyof T & string>(event: K, payload: T[K]): Promise<void>;
42
+ /**
43
+ * Subscribes to a specific event using a callback function.
44
+ * Returns an unsubscribe function to remove the listener.
45
+ *
46
+ * @remarks
47
+ * This method should be protected to avoid conflicts with `subscribe` method
48
+ */
49
+ protected abstract subscribeListener<K extends keyof T & string>(event: K, listener: (payload: T[K]) => void, options?: PublisherSubscribeListenerOptions): Promise<() => Promise<void>>;
50
+ /**
51
+ * Subscribes to a specific event using a callback function.
52
+ * Returns an unsubscribe function to remove the listener.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * const unsubscribe = publisher.subscribe('event', (payload) => {
57
+ * console.log(payload)
58
+ * }, {
59
+ * lastEventId,
60
+ * onError: (error) => {
61
+ * // handle error (consider unsubscribe if error can't be recovered)
62
+ * }
63
+ * })
64
+ *
65
+ * // Later
66
+ * unsubscribe()
67
+ * ```
68
+ */
69
+ subscribe<K extends keyof T & string>(event: K, listener: (payload: T[K]) => void, options?: PublisherSubscribeListenerOptions): Promise<() => Promise<void>>;
70
+ /**
71
+ * Subscribes to a specific event using an AsyncIteratorObject.
72
+ * Useful for `for await...of` loops with optional buffering and abort support.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * for await (const payload of publisher.subscribe('event', { signal, lastEventId })) {
77
+ * console.log(payload)
78
+ * }
79
+ * ```
80
+ */
81
+ subscribe<K extends keyof T & string>(event: K, options?: PublisherSubscribeIteratorOptions): AsyncIteratorClass<T[K], void, void>;
82
+ }
83
+
84
+ export { Publisher };
85
+ export type { PublisherOptions, PublisherSubscribeIteratorOptions, PublisherSubscribeListenerOptions };
@@ -0,0 +1,85 @@
1
+ import { ThrowableError, AsyncIteratorClass } from '@orpc/shared';
2
+
3
+ interface PublisherOptions {
4
+ /**
5
+ * Maximum number of events to buffer for AsyncIteratorObject subscribers.
6
+ *
7
+ * If the buffer exceeds this limit, the oldest event is dropped.
8
+ * This prevents unbounded memory growth if consumers process events slowly.
9
+ *
10
+ * Set to:
11
+ * - `0`: Disable buffering. Events must be consumed before the next one arrives.
12
+ * - `1`: Only keep the latest event. Useful for real-time updates where only the most recent value matters.
13
+ * - `Infinity`: Keep all events. Ensures no data loss, but may lead to high memory usage.
14
+ *
15
+ * @default 100
16
+ */
17
+ maxBufferedEvents?: number;
18
+ }
19
+ interface PublisherSubscribeListenerOptions {
20
+ /**
21
+ * Resume from a specific event ID
22
+ */
23
+ lastEventId?: string | undefined;
24
+ /**
25
+ * Triggered when an error occur
26
+ */
27
+ onError?: (error: ThrowableError) => void;
28
+ }
29
+ interface PublisherSubscribeIteratorOptions extends Pick<PublisherSubscribeListenerOptions, 'lastEventId'>, Pick<PublisherOptions, 'maxBufferedEvents'> {
30
+ /**
31
+ * Abort signal, automatically unsubscribes on abort
32
+ */
33
+ signal?: AbortSignal | undefined | null;
34
+ }
35
+ declare abstract class Publisher<T extends Record<string, object>> {
36
+ private readonly maxBufferedEvents;
37
+ constructor(options?: PublisherOptions);
38
+ /**
39
+ * Publish an event to subscribers
40
+ */
41
+ abstract publish<K extends keyof T & string>(event: K, payload: T[K]): Promise<void>;
42
+ /**
43
+ * Subscribes to a specific event using a callback function.
44
+ * Returns an unsubscribe function to remove the listener.
45
+ *
46
+ * @remarks
47
+ * This method should be protected to avoid conflicts with `subscribe` method
48
+ */
49
+ protected abstract subscribeListener<K extends keyof T & string>(event: K, listener: (payload: T[K]) => void, options?: PublisherSubscribeListenerOptions): Promise<() => Promise<void>>;
50
+ /**
51
+ * Subscribes to a specific event using a callback function.
52
+ * Returns an unsubscribe function to remove the listener.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * const unsubscribe = publisher.subscribe('event', (payload) => {
57
+ * console.log(payload)
58
+ * }, {
59
+ * lastEventId,
60
+ * onError: (error) => {
61
+ * // handle error (consider unsubscribe if error can't be recovered)
62
+ * }
63
+ * })
64
+ *
65
+ * // Later
66
+ * unsubscribe()
67
+ * ```
68
+ */
69
+ subscribe<K extends keyof T & string>(event: K, listener: (payload: T[K]) => void, options?: PublisherSubscribeListenerOptions): Promise<() => Promise<void>>;
70
+ /**
71
+ * Subscribes to a specific event using an AsyncIteratorObject.
72
+ * Useful for `for await...of` loops with optional buffering and abort support.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * for await (const payload of publisher.subscribe('event', { signal, lastEventId })) {
77
+ * console.log(payload)
78
+ * }
79
+ * ```
80
+ */
81
+ subscribe<K extends keyof T & string>(event: K, options?: PublisherSubscribeIteratorOptions): AsyncIteratorClass<T[K], void, void>;
82
+ }
83
+
84
+ export { Publisher };
85
+ export type { PublisherOptions, PublisherSubscribeIteratorOptions, PublisherSubscribeListenerOptions };
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ export { P as Publisher } from './shared/publisher.BSQf8ybR.mjs';
2
+ import '@orpc/shared';