orpc-fn 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 (128) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +250 -0
  3. package/dist/bound-call.d.ts +23 -0
  4. package/dist/bound-call.d.ts.map +1 -0
  5. package/dist/bound-call.js +23 -0
  6. package/dist/bound-call.js.map +1 -0
  7. package/dist/client.d.ts +44 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +49 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/create-fn.d.ts +97 -0
  12. package/dist/create-fn.d.ts.map +1 -0
  13. package/dist/create-fn.js +224 -0
  14. package/dist/create-fn.js.map +1 -0
  15. package/dist/expected-client-error.d.ts +11 -0
  16. package/dist/expected-client-error.d.ts.map +1 -0
  17. package/dist/expected-client-error.js +21 -0
  18. package/dist/expected-client-error.js.map +1 -0
  19. package/dist/expo.d.ts +66 -0
  20. package/dist/expo.d.ts.map +1 -0
  21. package/dist/expo.js +74 -0
  22. package/dist/expo.js.map +1 -0
  23. package/dist/hono.d.ts +81 -0
  24. package/dist/hono.d.ts.map +1 -0
  25. package/dist/hono.js +138 -0
  26. package/dist/hono.js.map +1 -0
  27. package/dist/index.d.ts +10 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +8 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/json-schema.d.ts +47 -0
  32. package/dist/json-schema.d.ts.map +1 -0
  33. package/dist/json-schema.js +44 -0
  34. package/dist/json-schema.js.map +1 -0
  35. package/dist/live/codec.d.ts +9 -0
  36. package/dist/live/codec.d.ts.map +1 -0
  37. package/dist/live/codec.js +27 -0
  38. package/dist/live/codec.js.map +1 -0
  39. package/dist/live/fn-live.d.ts +155 -0
  40. package/dist/live/fn-live.d.ts.map +1 -0
  41. package/dist/live/fn-live.js +210 -0
  42. package/dist/live/fn-live.js.map +1 -0
  43. package/dist/live/index.d.ts +4 -0
  44. package/dist/live/index.d.ts.map +1 -0
  45. package/dist/live/index.js +5 -0
  46. package/dist/live/index.js.map +1 -0
  47. package/dist/live/ioredis.d.ts +29 -0
  48. package/dist/live/ioredis.d.ts.map +1 -0
  49. package/dist/live/ioredis.js +49 -0
  50. package/dist/live/ioredis.js.map +1 -0
  51. package/dist/live/memory.d.ts +10 -0
  52. package/dist/live/memory.d.ts.map +1 -0
  53. package/dist/live/memory.js +45 -0
  54. package/dist/live/memory.js.map +1 -0
  55. package/dist/live/pub-sub.d.ts +211 -0
  56. package/dist/live/pub-sub.d.ts.map +1 -0
  57. package/dist/live/pub-sub.js +529 -0
  58. package/dist/live/pub-sub.js.map +1 -0
  59. package/dist/live/redis-bun.d.ts +26 -0
  60. package/dist/live/redis-bun.d.ts.map +1 -0
  61. package/dist/live/redis-bun.js +59 -0
  62. package/dist/live/redis-bun.js.map +1 -0
  63. package/dist/live/redis.d.ts +45 -0
  64. package/dist/live/redis.d.ts.map +1 -0
  65. package/dist/live/redis.js +214 -0
  66. package/dist/live/redis.js.map +1 -0
  67. package/dist/live/transport.d.ts +35 -0
  68. package/dist/live/transport.d.ts.map +1 -0
  69. package/dist/live/transport.js +2 -0
  70. package/dist/live/transport.js.map +1 -0
  71. package/dist/logger.d.ts +15 -0
  72. package/dist/logger.d.ts.map +1 -0
  73. package/dist/logger.js +15 -0
  74. package/dist/logger.js.map +1 -0
  75. package/dist/mastra.d.ts +44 -0
  76. package/dist/mastra.d.ts.map +1 -0
  77. package/dist/mastra.js +78 -0
  78. package/dist/mastra.js.map +1 -0
  79. package/dist/mcp.d.ts +39 -0
  80. package/dist/mcp.d.ts.map +1 -0
  81. package/dist/mcp.js +38 -0
  82. package/dist/mcp.js.map +1 -0
  83. package/dist/meta.d.ts +35 -0
  84. package/dist/meta.d.ts.map +1 -0
  85. package/dist/meta.js +30 -0
  86. package/dist/meta.js.map +1 -0
  87. package/dist/otel.d.ts +66 -0
  88. package/dist/otel.d.ts.map +1 -0
  89. package/dist/otel.js +63 -0
  90. package/dist/otel.js.map +1 -0
  91. package/dist/router.d.ts +13 -0
  92. package/dist/router.d.ts.map +1 -0
  93. package/dist/router.js +14 -0
  94. package/dist/router.js.map +1 -0
  95. package/dist/stream.d.ts +13 -0
  96. package/dist/stream.d.ts.map +1 -0
  97. package/dist/stream.js +44 -0
  98. package/dist/stream.js.map +1 -0
  99. package/dist/types.d.ts +196 -0
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +29 -0
  102. package/dist/types.js.map +1 -0
  103. package/package.json +157 -0
  104. package/src/bound-call.ts +50 -0
  105. package/src/client.ts +82 -0
  106. package/src/create-fn.ts +461 -0
  107. package/src/expected-client-error.ts +23 -0
  108. package/src/expo.ts +127 -0
  109. package/src/hono.ts +247 -0
  110. package/src/index.ts +45 -0
  111. package/src/json-schema.ts +82 -0
  112. package/src/live/codec.ts +31 -0
  113. package/src/live/fn-live.ts +470 -0
  114. package/src/live/index.ts +31 -0
  115. package/src/live/ioredis.ts +74 -0
  116. package/src/live/memory.ts +50 -0
  117. package/src/live/pub-sub.ts +828 -0
  118. package/src/live/redis-bun.ts +76 -0
  119. package/src/live/redis.ts +255 -0
  120. package/src/live/transport.ts +41 -0
  121. package/src/logger.ts +28 -0
  122. package/src/mastra.ts +140 -0
  123. package/src/mcp.ts +73 -0
  124. package/src/meta.ts +72 -0
  125. package/src/otel.ts +151 -0
  126. package/src/router.ts +15 -0
  127. package/src/stream.ts +54 -0
  128. package/src/types.ts +389 -0
@@ -0,0 +1,529 @@
1
+ import { ORPCError } from '@orpc/server';
2
+ import { errorMessageOf } from '../otel.js';
3
+ import { decodePayload, encodePayload } from './codec.js';
4
+ // ═══════════════════════════════════════════════════════════════════════════
5
+ // Bounded queue
6
+ // ═══════════════════════════════════════════════════════════════════════════
7
+ /**
8
+ * Bounded drop-oldest FIFO with "one overflow marker per overflow episode"
9
+ * semantics.
10
+ *
11
+ * On the FIRST overflow of an episode the oldest message is dropped and the
12
+ * marker from `createOverflowMarker` is enqueued INSTEAD of the new event - the
13
+ * subscriber's resync-from-cursor recovers the loss. While that marker is still
14
+ * queued, further overflows drop the oldest NON-marker message (the marker
15
+ * itself is never displaced) and enqueue the new event WITHOUT a second marker:
16
+ * the pending marker's resync already covers every drop that happens before it
17
+ * is delivered. Without this dedupe a sustained overflow converges the whole
18
+ * queue to markers - one full snapshot re-run per queued slot. Dequeuing the
19
+ * marker ends the episode; the next overflow starts a fresh one.
20
+ *
21
+ * `createOverflowMarker` absent (or returning null) falls back to plain
22
+ * drop-oldest with the new event enqueued.
23
+ */
24
+ export function createBoundedEventQueue(opts) {
25
+ const items = [];
26
+ // The queued marker while an overflow episode is open. Created once per
27
+ // episode, so identity is the exact "is this the pending marker?" test.
28
+ let pendingOverflowMarker = null;
29
+ return {
30
+ get length() {
31
+ return items.length;
32
+ },
33
+ enqueue(data) {
34
+ if (items.length >= opts.maxSize) {
35
+ if (pendingOverflowMarker !== null) {
36
+ // Episode already signalled: drop the oldest non-marker message
37
+ // and enqueue WITHOUT a second marker.
38
+ const dropIndex = items[0] === pendingOverflowMarker ? 1 : 0;
39
+ if (dropIndex >= items.length) {
40
+ // Degenerate maxSize: the queue is just the pending marker, so
41
+ // the new event is the drop - the marker's resync covers it.
42
+ opts.onDrop?.();
43
+ return;
44
+ }
45
+ items.splice(dropIndex, 1);
46
+ opts.onDrop?.();
47
+ items.push(data);
48
+ return;
49
+ }
50
+ items.shift();
51
+ opts.onDrop?.();
52
+ const marker = opts.createOverflowMarker?.() ?? null;
53
+ if (marker !== null) {
54
+ pendingOverflowMarker = marker;
55
+ items.push(marker);
56
+ return;
57
+ }
58
+ }
59
+ items.push(data);
60
+ },
61
+ dequeue() {
62
+ const next = items.shift();
63
+ if (next !== undefined && next === pendingOverflowMarker) {
64
+ pendingOverflowMarker = null;
65
+ }
66
+ return next;
67
+ }
68
+ };
69
+ }
70
+ /**
71
+ * One subscriber's ordered, bounded delivery. Events wait in a bounded
72
+ * drop-oldest queue (see {@link createBoundedEventQueue}) BEFORE the
73
+ * subscriber's filter runs; the filter runs in order as the consumer reads,
74
+ * so a slow async filter neither reorders events nor lets pending work grow
75
+ * past `maxSize`. Overflow markers skip the filter.
76
+ */
77
+ export function createSubscriberDelivery(options) {
78
+ const queue = createBoundedEventQueue({
79
+ maxSize: options.maxSize,
80
+ createOverflowMarker: () => {
81
+ const marker = options.overflowMarker?.() ?? null;
82
+ return marker === null ? null : { data: marker, marker: true };
83
+ },
84
+ onDrop: options.onDrop
85
+ });
86
+ let closed = false;
87
+ let wake = null;
88
+ const notify = () => {
89
+ wake?.();
90
+ wake = null;
91
+ };
92
+ async function* events() {
93
+ while (!closed) {
94
+ const item = queue.dequeue();
95
+ if (!item) {
96
+ await new Promise((resolve) => {
97
+ wake = resolve;
98
+ });
99
+ continue;
100
+ }
101
+ if (!item.marker && options.accept) {
102
+ try {
103
+ if (!(await options.accept(item.data)))
104
+ continue;
105
+ }
106
+ catch (error) {
107
+ options.onFilterError(error);
108
+ continue;
109
+ }
110
+ if (closed)
111
+ break;
112
+ }
113
+ yield item.data;
114
+ }
115
+ }
116
+ return {
117
+ push(data) {
118
+ if (closed)
119
+ return;
120
+ queue.enqueue({ data, marker: false });
121
+ notify();
122
+ },
123
+ events: events(),
124
+ /** Stop delivering; a parked read ends at once. */
125
+ close() {
126
+ closed = true;
127
+ notify();
128
+ }
129
+ };
130
+ }
131
+ // ═══════════════════════════════════════════════════════════════════════════
132
+ // Runtime (internal: the typed surface is what `createFn` returns)
133
+ // ═══════════════════════════════════════════════════════════════════════════
134
+ const DEFAULT_MAX_QUEUE_SIZE = 1000;
135
+ // Exponential resubscribe backoff with jitter so reconnecting subscribers don't stampede the broker.
136
+ const RESUBSCRIBE_BASE_DELAY_MS = 1000;
137
+ const RESUBSCRIBE_MAX_DELAY_MS = 30_000;
138
+ const RESUBSCRIBE_MAX_JITTER_MS = 250;
139
+ function getResubscribeDelayMs(attempt) {
140
+ const base = Math.min(RESUBSCRIBE_BASE_DELAY_MS * 2 ** attempt, RESUBSCRIBE_MAX_DELAY_MS);
141
+ return base + Math.floor(Math.random() * RESUBSCRIBE_MAX_JITTER_MS);
142
+ }
143
+ const resolveChannel = (channel, params, context) => (typeof channel === 'string' ? channel : channel(params, context));
144
+ /** @internal */
145
+ export function createLiveRuntime(options) {
146
+ const { tracing, createLogger } = options;
147
+ const logger = createLogger('pubsub', undefined);
148
+ // Deploy drain: every open subscription registers its close here. On
149
+ // SIGTERM the app ends them deliberately instead of letting the socket die
150
+ // with the process: the generator returns, oRPC closes the SSE response and
151
+ // the client's backoff reconnects to the new deployment.
152
+ const activeSubscriberCleanups = new Set();
153
+ const requireTransport = () => {
154
+ if (!options.pubsub) {
155
+ throw new Error('orpc-fn: live routes need a transport, pass createFn({ pubsub: { transport } })');
156
+ }
157
+ return options.pubsub.transport;
158
+ };
159
+ /**
160
+ * Shared publish pipeline: validate the raw event, resolve the channel from
161
+ * the parsed event, then publish the raw event (atomically maintaining the
162
+ * backlog when enabled) inside a producer span.
163
+ */
164
+ function createChannelPublisher(config) {
165
+ const { name, eventSchema, getChannelName, mirrorChannel } = config;
166
+ const backlog = config.useBacklog
167
+ ? { size: config.backlogSize ?? 50, ttlSeconds: config.backlogTtl ?? 30 }
168
+ : undefined;
169
+ const toMessages = async (items) => {
170
+ const messages = [];
171
+ for (const item of items) {
172
+ const data = (await eventSchema.parseAsync(item));
173
+ const channel = getChannelName(data);
174
+ const payload = encodePayload(item);
175
+ messages.push({ channel, payload });
176
+ if (mirrorChannel && mirrorChannel !== channel) {
177
+ messages.push({ channel: mirrorChannel, payload });
178
+ }
179
+ }
180
+ return messages;
181
+ };
182
+ const send = (operation, items) => tracing.inSpan(`${name}:${operation}`, 'PRODUCER', async (span) => {
183
+ const startTime = performance.now();
184
+ try {
185
+ span?.setAttribute('pubsub.operation', operation);
186
+ span?.setAttribute('pubsub.name', name);
187
+ span?.setAttribute('pubsub.batch_size', items.length);
188
+ const messages = await toMessages(items);
189
+ const first = messages[0];
190
+ if (first) {
191
+ span?.setAttribute('pubsub.channel', first.channel);
192
+ span?.setAttribute('pubsub.payload_size', first.payload.length);
193
+ }
194
+ await requireTransport().publish(messages, backlog);
195
+ }
196
+ finally {
197
+ span?.setAttribute('pubsub.duration_ms', performance.now() - startTime);
198
+ }
199
+ });
200
+ return {
201
+ publish: (data) => send('publish', [data]),
202
+ publishMany: async (items) => {
203
+ if (items.length > 0)
204
+ await send('publishMany', items);
205
+ }
206
+ };
207
+ }
208
+ /**
209
+ * Hubs for one pub/sub definition, keyed by channel. Many subscribers can
210
+ * share a channel (an org with many open tabs); one hub decodes and parses
211
+ * each payload once and fans the event out to every local subscriber,
212
+ * applying each subscriber's own filter. The hub also owns the broker
213
+ * subscription and its resubscribe backoff.
214
+ */
215
+ function createChannelHubs(config) {
216
+ const { eventSchema } = config;
217
+ const hubs = new Map();
218
+ const createHub = (channel) => {
219
+ const subscribers = new Set();
220
+ let closed = false;
221
+ let unsubscribe = null;
222
+ let attempt = 0;
223
+ let timer = null;
224
+ let markReady = () => { };
225
+ const ready = new Promise((resolve) => {
226
+ markReady = resolve;
227
+ });
228
+ // Parse in arrival order even when the schema is async.
229
+ // ponytail: unbounded while an async schema lags behind the broker;
230
+ // bound it if a channel can outpace its schema.
231
+ let parsing = Promise.resolve();
232
+ const deliver = async (raw) => {
233
+ let parsed;
234
+ try {
235
+ parsed = await eventSchema.parseAsync(decodePayload(raw));
236
+ }
237
+ catch (error) {
238
+ // Malformed payloads are dropped once for the whole channel.
239
+ logger.warn(`Dropped malformed message on ${channel}`, {
240
+ error: errorMessageOf(error)
241
+ });
242
+ return;
243
+ }
244
+ // Parsed once; each subscriber filters in its own delivery.
245
+ for (const sub of [...subscribers]) {
246
+ if (sub.isActive())
247
+ sub.push(parsed);
248
+ }
249
+ };
250
+ const release = (stale) => {
251
+ stale?.().catch((error) => {
252
+ logger.warn(`Failed to unsubscribe from ${channel}`, {
253
+ error: errorMessageOf(error)
254
+ });
255
+ });
256
+ };
257
+ const scheduleResubscribe = (reason) => {
258
+ if (closed || timer)
259
+ return;
260
+ const delayMs = getResubscribeDelayMs(attempt);
261
+ attempt++;
262
+ logger.warn(`Scheduling resubscribe for ${channel}`, {
263
+ reason,
264
+ attempt,
265
+ delayMs
266
+ });
267
+ timer = setTimeout(() => {
268
+ timer = null;
269
+ if (closed)
270
+ return;
271
+ release(unsubscribe);
272
+ unsubscribe = null;
273
+ void connect();
274
+ }, delayMs);
275
+ };
276
+ const connect = async () => {
277
+ try {
278
+ const next = await requireTransport().subscribe(channel, (raw) => {
279
+ parsing = parsing.then(() => deliver(raw));
280
+ }, (error) => {
281
+ logger.error(`Subscription to ${channel} lost`, {
282
+ error: error.message
283
+ });
284
+ scheduleResubscribe('lost');
285
+ });
286
+ if (closed)
287
+ return release(next);
288
+ unsubscribe = next;
289
+ attempt = 0;
290
+ logger.info(`Subscribed to ${channel}`);
291
+ }
292
+ catch (error) {
293
+ logger.error(`Failed to subscribe to ${channel}`, {
294
+ error: errorMessageOf(error)
295
+ });
296
+ scheduleResubscribe('subscribe-failed');
297
+ }
298
+ finally {
299
+ // A failed first attempt must not hold subscribers forever; the
300
+ // backoff keeps retrying in the background.
301
+ markReady();
302
+ }
303
+ };
304
+ void connect();
305
+ return {
306
+ subscribers,
307
+ ready,
308
+ close: () => {
309
+ closed = true;
310
+ if (timer)
311
+ clearTimeout(timer);
312
+ timer = null;
313
+ markReady();
314
+ release(unsubscribe);
315
+ unsubscribe = null;
316
+ }
317
+ };
318
+ };
319
+ return function addLocalSubscriber(channel, subscriber) {
320
+ let hub = hubs.get(channel);
321
+ if (!hub) {
322
+ hub = createHub(channel);
323
+ hubs.set(channel, hub);
324
+ }
325
+ hub.subscribers.add(subscriber);
326
+ const current = hub;
327
+ return {
328
+ ready: current.ready,
329
+ remove: () => {
330
+ current.subscribers.delete(subscriber);
331
+ if (current.subscribers.size === 0 && hubs.get(channel) === current) {
332
+ current.close();
333
+ hubs.delete(channel);
334
+ }
335
+ }
336
+ };
337
+ };
338
+ }
339
+ /** Publish and open subscriptions on one channel definition. */
340
+ function createChannel(channelOptions) {
341
+ const { name, channel, eventSchema, filterFn, authFn, overflowMarker, useBacklog = false } = channelOptions;
342
+ const getChannelName = (params, context) => resolveChannel(channel, params, context);
343
+ const addLocalSubscriber = createChannelHubs({ eventSchema });
344
+ const open = async ({ input, context, signal }) => {
345
+ const transport = requireTransport();
346
+ const channelName = getChannelName(input, context);
347
+ const startTime = performance.now();
348
+ const span = tracing.startSpan(`${name}:subscribe`, 'SERVER', {
349
+ 'pubsub.channel': channelName,
350
+ 'pubsub.operation': 'subscribe',
351
+ 'pubsub.name': name
352
+ });
353
+ const subscriberLogger = createLogger('pubsub', span);
354
+ try {
355
+ if (authFn !== undefined &&
356
+ authFn !== true &&
357
+ !(await authFn({ input, ctx: context }))) {
358
+ throw new ORPCError('FORBIDDEN', {
359
+ message: 'You do not have access to this subscription'
360
+ });
361
+ }
362
+ }
363
+ catch (error) {
364
+ tracing.end(span, error);
365
+ throw error;
366
+ }
367
+ subscriberLogger.info(`Subscribing to ${channelName}`);
368
+ let messageCount = 0;
369
+ let droppedCount = 0;
370
+ const delivery = createSubscriberDelivery({
371
+ maxSize: options.pubsub?.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,
372
+ accept: filterFn && ((data) => filterFn({ input, data })),
373
+ overflowMarker: overflowMarker && (() => overflowMarker(input)),
374
+ onFilterError: (error) => subscriberLogger.error('Error processing message', {
375
+ error: errorMessageOf(error)
376
+ }),
377
+ onDrop: () => {
378
+ droppedCount++;
379
+ options.pubsub?.onDrop?.(1, { name, channel: channelName });
380
+ }
381
+ });
382
+ // Live events wait here while the backlog replays, so replay comes first.
383
+ // ponytail: unbounded for the length of one backlog read.
384
+ let replaying = useBacklog ? [] : null;
385
+ const push = (data) => {
386
+ messageCount++;
387
+ if (replaying)
388
+ replaying.push(data);
389
+ else
390
+ delivery.push(data);
391
+ };
392
+ // The one way a subscription ends, whatever ends it: abort, drain, the
393
+ // stream finishing, or `close()`. Releases everything at once, even
394
+ // while the stream is parked or not yet read.
395
+ let closed = false;
396
+ const close = (...error) => {
397
+ if (closed)
398
+ return;
399
+ closed = true;
400
+ delivery.close();
401
+ activeSubscriberCleanups.delete(stop);
402
+ signal?.removeEventListener('abort', stop);
403
+ local.remove();
404
+ const durationMs = performance.now() - startTime;
405
+ span?.setAttribute('pubsub.duration_ms', durationMs);
406
+ span?.setAttribute('pubsub.message_count', messageCount);
407
+ span?.setAttribute('pubsub.dropped_count', droppedCount);
408
+ tracing.end(span, ...error);
409
+ subscriberLogger.info(`Unsubscribed from ${channelName}`, {
410
+ messageCount,
411
+ droppedCount,
412
+ durationMs: Math.round(durationMs)
413
+ });
414
+ };
415
+ const stop = () => close();
416
+ const local = addLocalSubscriber(channelName, {
417
+ isActive: () => !closed,
418
+ push
419
+ });
420
+ activeSubscriberCleanups.add(stop);
421
+ signal?.addEventListener('abort', stop);
422
+ if (signal?.aborted)
423
+ close();
424
+ // Subscribed before returning, so nothing published after `open`
425
+ // resolves is missed.
426
+ // ponytail: a failed first attempt also settles `ready` (the hub keeps
427
+ // retrying); events published before a retry succeeds are lost.
428
+ await local.ready;
429
+ if (replaying && !closed) {
430
+ try {
431
+ const items = await transport.readBacklog(channelName);
432
+ span?.setAttribute('pubsub.backlog_count', items.length);
433
+ for (const raw of items) {
434
+ if (closed)
435
+ break;
436
+ try {
437
+ messageCount++;
438
+ delivery.push(await eventSchema.parseAsync(decodePayload(raw)));
439
+ }
440
+ catch {
441
+ // Ignore malformed backlog entries
442
+ }
443
+ }
444
+ }
445
+ catch (error) {
446
+ // Live events still flow; only the backlog replay is lost.
447
+ subscriberLogger.warn(`Failed to replay backlog for ${channelName}`, {
448
+ error: errorMessageOf(error)
449
+ });
450
+ }
451
+ }
452
+ // ponytail: an event both in the backlog and arriving live is delivered
453
+ // twice; events carry no id to dedupe on.
454
+ for (const data of replaying ?? [])
455
+ delivery.push(data);
456
+ replaying = null;
457
+ async function* events() {
458
+ try {
459
+ yield* delivery.events;
460
+ }
461
+ finally {
462
+ close();
463
+ }
464
+ }
465
+ return { events: events(), close: () => close() };
466
+ };
467
+ const { publish, publishMany } = createChannelPublisher({
468
+ ...channelOptions,
469
+ getChannelName
470
+ });
471
+ return { open, publish, publishMany, getChannelName };
472
+ }
473
+ function createPubSub(pubsubOptions) {
474
+ const { name } = pubsubOptions;
475
+ const channel = createChannel(pubsubOptions);
476
+ const subscribe = options.buildProcedure({
477
+ name,
478
+ procedure: pubsubOptions.procedure,
479
+ meta: {},
480
+ route: {
481
+ method: 'GET',
482
+ tags: pubsubOptions.tags,
483
+ summary: pubsubOptions.summary ?? `Subscribe to ${name}`,
484
+ description: pubsubOptions.description
485
+ },
486
+ input: pubsubOptions.inputSchema,
487
+ handler: async function* ({ input, context, signal }) {
488
+ // The generator from `events()` releases everything when it ends.
489
+ yield* (await channel.open({ input: input, context, signal }))
490
+ .events;
491
+ }
492
+ });
493
+ const { publish, publishMany, getChannelName } = channel;
494
+ return { subscribe, publish, publishMany, getChannelName };
495
+ }
496
+ function createPublisher(publisherOptions) {
497
+ const getChannelName = (params) => resolveChannel(publisherOptions.channel, params);
498
+ const { publish, publishMany } = createChannelPublisher({
499
+ ...publisherOptions,
500
+ getChannelName
501
+ });
502
+ return { publish, publishMany, getChannelName };
503
+ }
504
+ return {
505
+ createChannel,
506
+ createPubSub,
507
+ createPublisher,
508
+ /** Open subscriptions of this instance (drain progress, gauges). */
509
+ activePubSubSubscriberCount: () => activeSubscriberCleanups.size,
510
+ /** End every open subscription; returns how many were closed. */
511
+ drainPubSubSubscribers: () => {
512
+ const cleanups = [...activeSubscriberCleanups];
513
+ activeSubscriberCleanups.clear();
514
+ for (const cleanup of cleanups) {
515
+ try {
516
+ cleanup();
517
+ }
518
+ catch (error) {
519
+ // A failed cleanup must not stop the drain.
520
+ logger.warn('Subscriber cleanup failed during drain', {
521
+ error: errorMessageOf(error)
522
+ });
523
+ }
524
+ }
525
+ return cleanups.length;
526
+ }
527
+ };
528
+ }
529
+ //# sourceMappingURL=pub-sub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pub-sub.js","sourceRoot":"","sources":["../../src/live/pub-sub.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Schema } from '@orpc/contract'\nimport { ORPCError } from '@orpc/server'\nimport type { ZodObject, ZodRawShape, ZodType, z } from 'zod'\nimport type { FnLogger } from '../logger.js'\nimport { errorMessageOf, type SpanLike, type Tracing } from '../otel.js'\nimport type {\n\tBuildProcedure,\n\tFnContext,\n\tFnDefinition,\n\tFnProcedure,\n\tMaybePromise,\n\tProcedureKey,\n\tProcedureOption\n} from '../types.js'\nimport { decodePayload, encodePayload } from './codec.js'\nimport type {\n\tBacklogOptions,\n\tPubSubMessage,\n\tPubSubTransport\n} from './transport.js'\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Types\n//\n// Publishers pass an event schema's INPUT (`z.input`); subscribers, filters\n// and channel resolvers see its OUTPUT (`z.output`). The raw input travels on\n// the wire and every receiver parses it once.\n// ═══════════════════════════════════════════════════════════════════════════\n\n/** Any Zod object schema, including `.strict()`, `.passthrough()` and `.catchall()`. */\nexport type ObjectSchema = ZodObject<ZodRawShape, z.core.$ZodObjectConfig>\n\n/**\n * A static channel name, or one derived from (partial) subscriber input or\n * event data.\n */\nexport type ChannelDefinition<TInput, TEventData, TContext = unknown> =\n\t| string\n\t| ((\n\t\t\tparams: Partial<TInput> | Partial<TEventData>,\n\t\t\tcontext?: TContext\n\t ) => string)\n\n/** Decides whether one event reaches one subscriber. */\nexport type FilterFn<TInput, TEventData> = (params: {\n\tinput: TInput\n\tdata: TEventData\n}) => MaybePromise<boolean>\n\n/**\n * Checked when a subscription opens, before anything is sent. `true` allows\n * everyone; a function returns false (or throws) to refuse with FORBIDDEN.\n */\nexport type AuthFn<TInput, TContext> =\n\t| ((params: { input: TInput; ctx: TContext }) => MaybePromise<boolean>)\n\t| true\n\nexport type PublisherConfig = {\n\t/** Required name for tracing (e.g. 'chat.messages'). */\n\tname: string\n\t/**\n\t * Store recent events in a backlog list; new subscribers receive it before\n\t * live events. Default: false\n\t */\n\tuseBacklog?: boolean\n\t/** Maximum backlog items to keep (default: 50). */\n\tbacklogSize?: number\n\t/** Backlog TTL in seconds (default: 30). */\n\tbacklogTtl?: number\n\t/**\n\t * Also publish every payload to this channel (with its own backlog when\n\t * `useBacklog`). Subscribers still use `channel`. Equal to the primary\n\t * channel means no duplicate work.\n\t */\n\tmirrorChannel?: string\n}\n\nexport type PubSubOptions<\n\tTDef extends FnDefinition,\n\tTInputSchema extends ObjectSchema,\n\tTEventSchema extends ZodType,\n\tTKey extends ProcedureKey<TDef>\n> = PublisherConfig & {\n\tchannel: ChannelDefinition<\n\t\tz.output<TInputSchema>,\n\t\tz.output<TEventSchema>,\n\t\tFnContext<TDef, TKey>\n\t>\n\t/** What the subscriber provides. */\n\tinputSchema: TInputSchema\n\t/** What gets published. */\n\teventSchema: TEventSchema\n\tfilterFn?: FilterFn<z.output<TInputSchema>, z.output<TEventSchema>>\n\tauthFn?: AuthFn<z.output<TInputSchema>, FnContext<TDef, TKey>>\n\t/**\n\t * When a slow subscriber's queue overflows (drop-oldest), enqueue this\n\t * marker INSTEAD of the new event so the subscriber resyncs from its\n\t * cursor. A wholly dropped item leaves no per-item gap a consumer could\n\t * detect, so the marker is the only reliable live signal of loss. Return\n\t * null to skip.\n\t */\n\toverflowMarker?: (\n\t\tinput: z.output<TInputSchema>\n\t) => z.output<TEventSchema> | null\n\ttags?: TDef['tag'][]\n\tsummary?: string\n\tdescription?: string\n}\n\nexport type PubSub<\n\tTDef extends FnDefinition,\n\tTInputSchema extends ObjectSchema,\n\tTEventSchema extends ZodType,\n\tTKey extends ProcedureKey<TDef>\n> = {\n\tsubscribe: FnProcedure<\n\t\tTDef,\n\t\tTKey,\n\t\tTInputSchema,\n\t\tSchema<\n\t\t\tAsyncGenerator<z.output<TEventSchema>, void, unknown>,\n\t\t\tAsyncGenerator<z.output<TEventSchema>, void, unknown>\n\t\t>\n\t>\n\tpublish: (data: z.input<TEventSchema>) => Promise<void>\n\t/** Publish a batch in one atomic round-trip; items may target different channels. */\n\tpublishMany: (items: readonly z.input<TEventSchema>[]) => Promise<void>\n\tgetChannelName: (\n\t\tparams: Partial<z.output<TInputSchema>> | Partial<z.output<TEventSchema>>,\n\t\tcontext?: FnContext<TDef, TKey>\n\t) => string\n}\n\nexport type CreatePubSub<TDef extends FnDefinition> = <\n\tTInputSchema extends ObjectSchema,\n\tTEventSchema extends ZodType,\n\tTKey extends ProcedureKey<TDef> = TDef['default']\n>(\n\toptions: PubSubOptions<TDef, TInputSchema, TEventSchema, NoInfer<TKey>> &\n\t\t/** Which `procedures` builder the subscribe route uses. */\n\t\tProcedureOption<TDef, TKey>\n) => PubSub<TDef, TInputSchema, TEventSchema, TKey>\n\nexport type PublisherOptions<TEventSchema extends ZodType> = PublisherConfig & {\n\t/** Static channel name, or a resolver over (partial) parsed event data. */\n\tchannel: string | ((params: Partial<z.output<TEventSchema>>) => string)\n\teventSchema: TEventSchema\n}\n\nexport type Publisher<TEventSchema extends ZodType> = {\n\tpublish: (data: z.input<TEventSchema>) => Promise<void>\n\tpublishMany: (items: readonly z.input<TEventSchema>[]) => Promise<void>\n\tgetChannelName: (params: Partial<z.output<TEventSchema>>) => string\n}\n\n/**\n * Publish-only counterpart to `createPubSub`, for buses whose subscribe side\n * lives elsewhere (e.g. an `fnLive` route sharing the channel and schema).\n */\nexport type CreatePublisher = <TEventSchema extends ZodType>(\n\toptions: PublisherOptions<TEventSchema>\n) => Publisher<TEventSchema>\n\nexport type PubSubRuntimeOptions = {\n\ttransport: PubSubTransport\n\t/**\n\t * Called once per event dropped from a slow subscriber's queue. Events wait\n\t * there before the subscriber's filter runs, so drops count unfiltered events.\n\t */\n\tonDrop?: (count: number, info: { name: string; channel: string }) => void\n\t/** Per-subscriber queue bound before drop-oldest (default: 1000). */\n\tmaxQueueSize?: number\n}\n\n/** @internal */\nexport type LiveRuntimeOptions = {\n\tbuildProcedure: BuildProcedure\n\ttracing: Tracing\n\tcreateLogger: (scope: string, span: SpanLike | undefined) => FnLogger\n\tpubsub?: PubSubRuntimeOptions | undefined\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Bounded queue\n// ═══════════════════════════════════════════════════════════════════════════\n\n/**\n * Bounded drop-oldest FIFO with \"one overflow marker per overflow episode\"\n * semantics.\n *\n * On the FIRST overflow of an episode the oldest message is dropped and the\n * marker from `createOverflowMarker` is enqueued INSTEAD of the new event - the\n * subscriber's resync-from-cursor recovers the loss. While that marker is still\n * queued, further overflows drop the oldest NON-marker message (the marker\n * itself is never displaced) and enqueue the new event WITHOUT a second marker:\n * the pending marker's resync already covers every drop that happens before it\n * is delivered. Without this dedupe a sustained overflow converges the whole\n * queue to markers - one full snapshot re-run per queued slot. Dequeuing the\n * marker ends the episode; the next overflow starts a fresh one.\n *\n * `createOverflowMarker` absent (or returning null) falls back to plain\n * drop-oldest with the new event enqueued.\n */\nexport function createBoundedEventQueue<T>(opts: {\n\tmaxSize: number\n\tcreateOverflowMarker?: () => T | null\n\t/** Called once per dropped message (metrics/counters). */\n\tonDrop?: () => void\n}) {\n\tconst items: T[] = []\n\t// The queued marker while an overflow episode is open. Created once per\n\t// episode, so identity is the exact \"is this the pending marker?\" test.\n\tlet pendingOverflowMarker: T | null = null\n\n\treturn {\n\t\tget length() {\n\t\t\treturn items.length\n\t\t},\n\t\tenqueue(data: T): void {\n\t\t\tif (items.length >= opts.maxSize) {\n\t\t\t\tif (pendingOverflowMarker !== null) {\n\t\t\t\t\t// Episode already signalled: drop the oldest non-marker message\n\t\t\t\t\t// and enqueue WITHOUT a second marker.\n\t\t\t\t\tconst dropIndex = items[0] === pendingOverflowMarker ? 1 : 0\n\t\t\t\t\tif (dropIndex >= items.length) {\n\t\t\t\t\t\t// Degenerate maxSize: the queue is just the pending marker, so\n\t\t\t\t\t\t// the new event is the drop - the marker's resync covers it.\n\t\t\t\t\t\topts.onDrop?.()\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\titems.splice(dropIndex, 1)\n\t\t\t\t\topts.onDrop?.()\n\t\t\t\t\titems.push(data)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\titems.shift()\n\t\t\t\topts.onDrop?.()\n\t\t\t\tconst marker = opts.createOverflowMarker?.() ?? null\n\t\t\t\tif (marker !== null) {\n\t\t\t\t\tpendingOverflowMarker = marker\n\t\t\t\t\titems.push(marker)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\titems.push(data)\n\t\t},\n\t\tdequeue(): T | undefined {\n\t\t\tconst next = items.shift()\n\t\t\tif (next !== undefined && next === pendingOverflowMarker) {\n\t\t\t\tpendingOverflowMarker = null\n\t\t\t}\n\t\t\treturn next\n\t\t}\n\t}\n}\n\n/**\n * One subscriber's ordered, bounded delivery. Events wait in a bounded\n * drop-oldest queue (see {@link createBoundedEventQueue}) BEFORE the\n * subscriber's filter runs; the filter runs in order as the consumer reads,\n * so a slow async filter neither reorders events nor lets pending work grow\n * past `maxSize`. Overflow markers skip the filter.\n */\nexport function createSubscriberDelivery<T>(options: {\n\tmaxSize: number\n\taccept?: ((data: T) => MaybePromise<boolean>) | undefined\n\toverflowMarker?: (() => T | null) | undefined\n\tonFilterError: (error: unknown) => void\n\tonDrop: () => void\n}) {\n\ttype Item = { data: T; marker: boolean }\n\tconst queue = createBoundedEventQueue<Item>({\n\t\tmaxSize: options.maxSize,\n\t\tcreateOverflowMarker: () => {\n\t\t\tconst marker = options.overflowMarker?.() ?? null\n\t\t\treturn marker === null ? null : { data: marker, marker: true }\n\t\t},\n\t\tonDrop: options.onDrop\n\t})\n\tlet closed = false\n\tlet wake: (() => void) | null = null\n\tconst notify = () => {\n\t\twake?.()\n\t\twake = null\n\t}\n\n\tasync function* events(): AsyncGenerator<T, void, unknown> {\n\t\twhile (!closed) {\n\t\t\tconst item = queue.dequeue()\n\t\t\tif (!item) {\n\t\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\t\twake = resolve\n\t\t\t\t})\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif (!item.marker && options.accept) {\n\t\t\t\ttry {\n\t\t\t\t\tif (!(await options.accept(item.data))) continue\n\t\t\t\t} catch (error) {\n\t\t\t\t\toptions.onFilterError(error)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (closed) break\n\t\t\t}\n\t\t\tyield item.data\n\t\t}\n\t}\n\n\treturn {\n\t\tpush(data: T) {\n\t\t\tif (closed) return\n\t\t\tqueue.enqueue({ data, marker: false })\n\t\t\tnotify()\n\t\t},\n\t\tevents: events(),\n\t\t/** Stop delivering; a parked read ends at once. */\n\t\tclose() {\n\t\t\tclosed = true\n\t\t\tnotify()\n\t\t}\n\t}\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Runtime (internal: the typed surface is what `createFn` returns)\n// ═══════════════════════════════════════════════════════════════════════════\n\nconst DEFAULT_MAX_QUEUE_SIZE = 1000\n\n// Exponential resubscribe backoff with jitter so reconnecting subscribers don't stampede the broker.\nconst RESUBSCRIBE_BASE_DELAY_MS = 1000\nconst RESUBSCRIBE_MAX_DELAY_MS = 30_000\nconst RESUBSCRIBE_MAX_JITTER_MS = 250\n\nfunction getResubscribeDelayMs(attempt: number): number {\n\tconst base = Math.min(\n\t\tRESUBSCRIBE_BASE_DELAY_MS * 2 ** attempt,\n\t\tRESUBSCRIBE_MAX_DELAY_MS\n\t)\n\treturn base + Math.floor(Math.random() * RESUBSCRIBE_MAX_JITTER_MS)\n}\n\ntype Input = Record<string, unknown>\n\ntype LocalSubscriber = {\n\tisActive: () => boolean\n\tpush: (data: unknown) => void\n}\n\ntype Hub = {\n\tsubscribers: Set<LocalSubscriber>\n\t/** Settles after the first subscribe attempt (or when the hub closes). */\n\tready: Promise<void>\n\tclose: () => void\n}\n\n/** An open subscription: registered, authorized and subscribed. */\nexport type LiveSubscription = {\n\t/** Live events, after the backlog replay. Ends when closed or aborted. */\n\tevents: AsyncGenerator<unknown, void, unknown>\n\t/** Release everything; safe to call more than once, and before `events` runs. */\n\tclose: () => void\n}\n\n/** @internal Untyped options; `PubSubOptions` is the typed surface. */\nexport type ChannelOptions = PublisherConfig & {\n\tchannel: ChannelDefinition<Input, unknown>\n\teventSchema: ZodType\n\tfilterFn?: FilterFn<Input, unknown>\n\tauthFn?: AuthFn<Input, unknown>\n\toverflowMarker?: (input: Input) => unknown\n}\n\n/** @internal */\nexport type Channel = {\n\t/** Authorize and subscribe; resolves once events can be delivered. */\n\topen: (options: {\n\t\tinput: Input\n\t\tcontext: unknown\n\t\tsignal: AbortSignal | undefined\n\t}) => Promise<LiveSubscription>\n\tpublish: (data: unknown) => Promise<void>\n\tpublishMany: (items: readonly unknown[]) => Promise<void>\n\tgetChannelName: (params: Input, context?: unknown) => string\n}\n\nconst resolveChannel = (\n\tchannel: string | ((params: Input, context?: unknown) => string),\n\tparams: Input,\n\tcontext?: unknown\n) => (typeof channel === 'string' ? channel : channel(params, context))\n\n/** @internal */\nexport function createLiveRuntime(options: LiveRuntimeOptions) {\n\tconst { tracing, createLogger } = options\n\tconst logger = createLogger('pubsub', undefined)\n\t// Deploy drain: every open subscription registers its close here. On\n\t// SIGTERM the app ends them deliberately instead of letting the socket die\n\t// with the process: the generator returns, oRPC closes the SSE response and\n\t// the client's backoff reconnects to the new deployment.\n\tconst activeSubscriberCleanups = new Set<() => void>()\n\n\tconst requireTransport = () => {\n\t\tif (!options.pubsub) {\n\t\t\tthrow new Error(\n\t\t\t\t'orpc-fn: live routes need a transport, pass createFn({ pubsub: { transport } })'\n\t\t\t)\n\t\t}\n\t\treturn options.pubsub.transport\n\t}\n\n\t/**\n\t * Shared publish pipeline: validate the raw event, resolve the channel from\n\t * the parsed event, then publish the raw event (atomically maintaining the\n\t * backlog when enabled) inside a producer span.\n\t */\n\tfunction createChannelPublisher(\n\t\tconfig: PublisherConfig & {\n\t\t\teventSchema: ZodType\n\t\t\tgetChannelName: (data: Input) => string\n\t\t}\n\t) {\n\t\tconst { name, eventSchema, getChannelName, mirrorChannel } = config\n\t\tconst backlog: BacklogOptions | undefined = config.useBacklog\n\t\t\t? { size: config.backlogSize ?? 50, ttlSeconds: config.backlogTtl ?? 30 }\n\t\t\t: undefined\n\n\t\tconst toMessages = async (items: readonly unknown[]) => {\n\t\t\tconst messages: PubSubMessage[] = []\n\t\t\tfor (const item of items) {\n\t\t\t\tconst data = (await eventSchema.parseAsync(item)) as Input\n\t\t\t\tconst channel = getChannelName(data)\n\t\t\t\tconst payload = encodePayload(item)\n\t\t\t\tmessages.push({ channel, payload })\n\t\t\t\tif (mirrorChannel && mirrorChannel !== channel) {\n\t\t\t\t\tmessages.push({ channel: mirrorChannel, payload })\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn messages\n\t\t}\n\n\t\tconst send = (\n\t\t\toperation: 'publish' | 'publishMany',\n\t\t\titems: readonly unknown[]\n\t\t) =>\n\t\t\ttracing.inSpan(`${name}:${operation}`, 'PRODUCER', async (span) => {\n\t\t\t\tconst startTime = performance.now()\n\t\t\t\ttry {\n\t\t\t\t\tspan?.setAttribute('pubsub.operation', operation)\n\t\t\t\t\tspan?.setAttribute('pubsub.name', name)\n\t\t\t\t\tspan?.setAttribute('pubsub.batch_size', items.length)\n\t\t\t\t\tconst messages = await toMessages(items)\n\t\t\t\t\tconst first = messages[0]\n\t\t\t\t\tif (first) {\n\t\t\t\t\t\tspan?.setAttribute('pubsub.channel', first.channel)\n\t\t\t\t\t\tspan?.setAttribute('pubsub.payload_size', first.payload.length)\n\t\t\t\t\t}\n\t\t\t\t\tawait requireTransport().publish(messages, backlog)\n\t\t\t\t} finally {\n\t\t\t\t\tspan?.setAttribute(\n\t\t\t\t\t\t'pubsub.duration_ms',\n\t\t\t\t\t\tperformance.now() - startTime\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t})\n\n\t\treturn {\n\t\t\tpublish: (data: unknown) => send('publish', [data]),\n\t\t\tpublishMany: async (items: readonly unknown[]) => {\n\t\t\t\tif (items.length > 0) await send('publishMany', items)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Hubs for one pub/sub definition, keyed by channel. Many subscribers can\n\t * share a channel (an org with many open tabs); one hub decodes and parses\n\t * each payload once and fans the event out to every local subscriber,\n\t * applying each subscriber's own filter. The hub also owns the broker\n\t * subscription and its resubscribe backoff.\n\t */\n\tfunction createChannelHubs(config: { eventSchema: ZodType }) {\n\t\tconst { eventSchema } = config\n\t\tconst hubs = new Map<string, Hub>()\n\n\t\tconst createHub = (channel: string): Hub => {\n\t\t\tconst subscribers = new Set<LocalSubscriber>()\n\t\t\tlet closed = false\n\t\t\tlet unsubscribe: (() => Promise<void>) | null = null\n\t\t\tlet attempt = 0\n\t\t\tlet timer: ReturnType<typeof setTimeout> | null = null\n\t\t\tlet markReady = () => {}\n\t\t\tconst ready = new Promise<void>((resolve) => {\n\t\t\t\tmarkReady = resolve\n\t\t\t})\n\t\t\t// Parse in arrival order even when the schema is async.\n\t\t\t// ponytail: unbounded while an async schema lags behind the broker;\n\t\t\t// bound it if a channel can outpace its schema.\n\t\t\tlet parsing = Promise.resolve()\n\n\t\t\tconst deliver = async (raw: string) => {\n\t\t\t\tlet parsed: unknown\n\t\t\t\ttry {\n\t\t\t\t\tparsed = await eventSchema.parseAsync(decodePayload(raw))\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Malformed payloads are dropped once for the whole channel.\n\t\t\t\t\tlogger.warn(`Dropped malformed message on ${channel}`, {\n\t\t\t\t\t\terror: errorMessageOf(error)\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Parsed once; each subscriber filters in its own delivery.\n\t\t\t\tfor (const sub of [...subscribers]) {\n\t\t\t\t\tif (sub.isActive()) sub.push(parsed)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst release = (stale: (() => Promise<void>) | null) => {\n\t\t\t\tstale?.().catch((error) => {\n\t\t\t\t\tlogger.warn(`Failed to unsubscribe from ${channel}`, {\n\t\t\t\t\t\terror: errorMessageOf(error)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tconst scheduleResubscribe = (reason: string) => {\n\t\t\t\tif (closed || timer) return\n\t\t\t\tconst delayMs = getResubscribeDelayMs(attempt)\n\t\t\t\tattempt++\n\t\t\t\tlogger.warn(`Scheduling resubscribe for ${channel}`, {\n\t\t\t\t\treason,\n\t\t\t\t\tattempt,\n\t\t\t\t\tdelayMs\n\t\t\t\t})\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\ttimer = null\n\t\t\t\t\tif (closed) return\n\t\t\t\t\trelease(unsubscribe)\n\t\t\t\t\tunsubscribe = null\n\t\t\t\t\tvoid connect()\n\t\t\t\t}, delayMs)\n\t\t\t}\n\n\t\t\tconst connect = async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst next = await requireTransport().subscribe(\n\t\t\t\t\t\tchannel,\n\t\t\t\t\t\t(raw) => {\n\t\t\t\t\t\t\tparsing = parsing.then(() => deliver(raw))\n\t\t\t\t\t\t},\n\t\t\t\t\t\t(error) => {\n\t\t\t\t\t\t\tlogger.error(`Subscription to ${channel} lost`, {\n\t\t\t\t\t\t\t\terror: error.message\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tscheduleResubscribe('lost')\n\t\t\t\t\t\t}\n\t\t\t\t\t)\n\t\t\t\t\tif (closed) return release(next)\n\t\t\t\t\tunsubscribe = next\n\t\t\t\t\tattempt = 0\n\t\t\t\t\tlogger.info(`Subscribed to ${channel}`)\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogger.error(`Failed to subscribe to ${channel}`, {\n\t\t\t\t\t\terror: errorMessageOf(error)\n\t\t\t\t\t})\n\t\t\t\t\tscheduleResubscribe('subscribe-failed')\n\t\t\t\t} finally {\n\t\t\t\t\t// A failed first attempt must not hold subscribers forever; the\n\t\t\t\t\t// backoff keeps retrying in the background.\n\t\t\t\t\tmarkReady()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvoid connect()\n\n\t\t\treturn {\n\t\t\t\tsubscribers,\n\t\t\t\tready,\n\t\t\t\tclose: () => {\n\t\t\t\t\tclosed = true\n\t\t\t\t\tif (timer) clearTimeout(timer)\n\t\t\t\t\ttimer = null\n\t\t\t\t\tmarkReady()\n\t\t\t\t\trelease(unsubscribe)\n\t\t\t\t\tunsubscribe = null\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn function addLocalSubscriber(\n\t\t\tchannel: string,\n\t\t\tsubscriber: LocalSubscriber\n\t\t) {\n\t\t\tlet hub = hubs.get(channel)\n\t\t\tif (!hub) {\n\t\t\t\thub = createHub(channel)\n\t\t\t\thubs.set(channel, hub)\n\t\t\t}\n\t\t\thub.subscribers.add(subscriber)\n\t\t\tconst current = hub\n\t\t\treturn {\n\t\t\t\tready: current.ready,\n\t\t\t\tremove: () => {\n\t\t\t\t\tcurrent.subscribers.delete(subscriber)\n\t\t\t\t\tif (current.subscribers.size === 0 && hubs.get(channel) === current) {\n\t\t\t\t\t\tcurrent.close()\n\t\t\t\t\t\thubs.delete(channel)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Publish and open subscriptions on one channel definition. */\n\tfunction createChannel(channelOptions: ChannelOptions): Channel {\n\t\tconst {\n\t\t\tname,\n\t\t\tchannel,\n\t\t\teventSchema,\n\t\t\tfilterFn,\n\t\t\tauthFn,\n\t\t\toverflowMarker,\n\t\t\tuseBacklog = false\n\t\t} = channelOptions\n\n\t\tconst getChannelName = (params: Input, context?: unknown) =>\n\t\t\tresolveChannel(channel, params, context)\n\t\tconst addLocalSubscriber = createChannelHubs({ eventSchema })\n\n\t\tconst open: Channel['open'] = async ({ input, context, signal }) => {\n\t\t\tconst transport = requireTransport()\n\t\t\tconst channelName = getChannelName(input, context)\n\t\t\tconst startTime = performance.now()\n\t\t\tconst span = tracing.startSpan(`${name}:subscribe`, 'SERVER', {\n\t\t\t\t'pubsub.channel': channelName,\n\t\t\t\t'pubsub.operation': 'subscribe',\n\t\t\t\t'pubsub.name': name\n\t\t\t})\n\t\t\tconst subscriberLogger = createLogger('pubsub', span)\n\n\t\t\ttry {\n\t\t\t\tif (\n\t\t\t\t\tauthFn !== undefined &&\n\t\t\t\t\tauthFn !== true &&\n\t\t\t\t\t!(await authFn({ input, ctx: context }))\n\t\t\t\t) {\n\t\t\t\t\tthrow new ORPCError('FORBIDDEN', {\n\t\t\t\t\t\tmessage: 'You do not have access to this subscription'\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\ttracing.end(span, error)\n\t\t\t\tthrow error\n\t\t\t}\n\n\t\t\tsubscriberLogger.info(`Subscribing to ${channelName}`)\n\n\t\t\tlet messageCount = 0\n\t\t\tlet droppedCount = 0\n\t\t\tconst delivery = createSubscriberDelivery<unknown>({\n\t\t\t\tmaxSize: options.pubsub?.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,\n\t\t\t\taccept: filterFn && ((data) => filterFn({ input, data })),\n\t\t\t\toverflowMarker: overflowMarker && (() => overflowMarker(input)),\n\t\t\t\tonFilterError: (error) =>\n\t\t\t\t\tsubscriberLogger.error('Error processing message', {\n\t\t\t\t\t\terror: errorMessageOf(error)\n\t\t\t\t\t}),\n\t\t\t\tonDrop: () => {\n\t\t\t\t\tdroppedCount++\n\t\t\t\t\toptions.pubsub?.onDrop?.(1, { name, channel: channelName })\n\t\t\t\t}\n\t\t\t})\n\t\t\t// Live events wait here while the backlog replays, so replay comes first.\n\t\t\t// ponytail: unbounded for the length of one backlog read.\n\t\t\tlet replaying: unknown[] | null = useBacklog ? [] : null\n\t\t\tconst push = (data: unknown) => {\n\t\t\t\tmessageCount++\n\t\t\t\tif (replaying) replaying.push(data)\n\t\t\t\telse delivery.push(data)\n\t\t\t}\n\n\t\t\t// The one way a subscription ends, whatever ends it: abort, drain, the\n\t\t\t// stream finishing, or `close()`. Releases everything at once, even\n\t\t\t// while the stream is parked or not yet read.\n\t\t\tlet closed = false\n\t\t\tconst close = (...error: [] | [unknown]) => {\n\t\t\t\tif (closed) return\n\t\t\t\tclosed = true\n\t\t\t\tdelivery.close()\n\t\t\t\tactiveSubscriberCleanups.delete(stop)\n\t\t\t\tsignal?.removeEventListener('abort', stop)\n\t\t\t\tlocal.remove()\n\t\t\t\tconst durationMs = performance.now() - startTime\n\t\t\t\tspan?.setAttribute('pubsub.duration_ms', durationMs)\n\t\t\t\tspan?.setAttribute('pubsub.message_count', messageCount)\n\t\t\t\tspan?.setAttribute('pubsub.dropped_count', droppedCount)\n\t\t\t\ttracing.end(span, ...error)\n\t\t\t\tsubscriberLogger.info(`Unsubscribed from ${channelName}`, {\n\t\t\t\t\tmessageCount,\n\t\t\t\t\tdroppedCount,\n\t\t\t\t\tdurationMs: Math.round(durationMs)\n\t\t\t\t})\n\t\t\t}\n\t\t\tconst stop = () => close()\n\t\t\tconst local = addLocalSubscriber(channelName, {\n\t\t\t\tisActive: () => !closed,\n\t\t\t\tpush\n\t\t\t})\n\t\t\tactiveSubscriberCleanups.add(stop)\n\t\t\tsignal?.addEventListener('abort', stop)\n\t\t\tif (signal?.aborted) close()\n\n\t\t\t// Subscribed before returning, so nothing published after `open`\n\t\t\t// resolves is missed.\n\t\t\t// ponytail: a failed first attempt also settles `ready` (the hub keeps\n\t\t\t// retrying); events published before a retry succeeds are lost.\n\t\t\tawait local.ready\n\t\t\tif (replaying && !closed) {\n\t\t\t\ttry {\n\t\t\t\t\tconst items = await transport.readBacklog(channelName)\n\t\t\t\t\tspan?.setAttribute('pubsub.backlog_count', items.length)\n\t\t\t\t\tfor (const raw of items) {\n\t\t\t\t\t\tif (closed) break\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tmessageCount++\n\t\t\t\t\t\t\tdelivery.push(await eventSchema.parseAsync(decodePayload(raw)))\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// Ignore malformed backlog entries\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Live events still flow; only the backlog replay is lost.\n\t\t\t\t\tsubscriberLogger.warn(`Failed to replay backlog for ${channelName}`, {\n\t\t\t\t\t\terror: errorMessageOf(error)\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\t// ponytail: an event both in the backlog and arriving live is delivered\n\t\t\t// twice; events carry no id to dedupe on.\n\t\t\tfor (const data of replaying ?? []) delivery.push(data)\n\t\t\treplaying = null\n\n\t\t\tasync function* events() {\n\t\t\t\ttry {\n\t\t\t\t\tyield* delivery.events\n\t\t\t\t} finally {\n\t\t\t\t\tclose()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { events: events(), close: () => close() }\n\t\t}\n\n\t\tconst { publish, publishMany } = createChannelPublisher({\n\t\t\t...channelOptions,\n\t\t\tgetChannelName\n\t\t})\n\n\t\treturn { open, publish, publishMany, getChannelName }\n\t}\n\n\tfunction createPubSub(\n\t\tpubsubOptions: ChannelOptions & {\n\t\t\tinputSchema: ZodType\n\t\t\tprocedure?: string\n\t\t\ttags?: string[]\n\t\t\tsummary?: string\n\t\t\tdescription?: string\n\t\t}\n\t) {\n\t\tconst { name } = pubsubOptions\n\t\tconst channel = createChannel(pubsubOptions)\n\t\tconst subscribe = options.buildProcedure({\n\t\t\tname,\n\t\t\tprocedure: pubsubOptions.procedure,\n\t\t\tmeta: {},\n\t\t\troute: {\n\t\t\t\tmethod: 'GET',\n\t\t\t\ttags: pubsubOptions.tags,\n\t\t\t\tsummary: pubsubOptions.summary ?? `Subscribe to ${name}`,\n\t\t\t\tdescription: pubsubOptions.description\n\t\t\t},\n\t\t\tinput: pubsubOptions.inputSchema,\n\t\t\thandler: async function* ({ input, context, signal }) {\n\t\t\t\t// The generator from `events()` releases everything when it ends.\n\t\t\t\tyield* (await channel.open({ input: input as Input, context, signal }))\n\t\t\t\t\t.events\n\t\t\t}\n\t\t})\n\n\t\tconst { publish, publishMany, getChannelName } = channel\n\t\treturn { subscribe, publish, publishMany, getChannelName }\n\t}\n\n\tfunction createPublisher(publisherOptions: PublisherOptions<ZodType>) {\n\t\tconst getChannelName = (params: Input) =>\n\t\t\tresolveChannel(publisherOptions.channel, params)\n\t\tconst { publish, publishMany } = createChannelPublisher({\n\t\t\t...publisherOptions,\n\t\t\tgetChannelName\n\t\t})\n\t\treturn { publish, publishMany, getChannelName }\n\t}\n\n\treturn {\n\t\tcreateChannel,\n\t\tcreatePubSub,\n\t\tcreatePublisher,\n\t\t/** Open subscriptions of this instance (drain progress, gauges). */\n\t\tactivePubSubSubscriberCount: () => activeSubscriberCleanups.size,\n\t\t/** End every open subscription; returns how many were closed. */\n\t\tdrainPubSubSubscribers: () => {\n\t\t\tconst cleanups = [...activeSubscriberCleanups]\n\t\t\tactiveSubscriberCleanups.clear()\n\t\t\tfor (const cleanup of cleanups) {\n\t\t\t\ttry {\n\t\t\t\t\tcleanup()\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// A failed cleanup must not stop the drain.\n\t\t\t\t\tlogger.warn('Subscriber cleanup failed during drain', {\n\t\t\t\t\t\terror: errorMessageOf(error)\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn cleanups.length\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,26 @@
1
+ type Listener = (message: string, channel: string) => void;
2
+ /** The part of Bun's `RedisClient` this transport uses. */
3
+ export interface BunRedisClientLike {
4
+ send(command: string, args: string[]): Promise<unknown>;
5
+ subscribe(channel: string, listener: Listener): Promise<unknown>;
6
+ unsubscribe(channel: string, listener?: Listener): Promise<unknown>;
7
+ duplicate(): Promise<BunRedisClientLike>;
8
+ close(): void;
9
+ onconnect: ((this: never) => void) | null;
10
+ onclose: ((this: never, error: Error) => void) | null;
11
+ }
12
+ /**
13
+ * `PubSubTransport` for Bun's built-in `RedisClient`. Subscriptions use a
14
+ * dedicated connection (`client.duplicate()` unless `subscriber` is given)
15
+ * that re-subscribes its channels after every reconnect; configure retries on
16
+ * the client (`maxRetries`) since the duplicate copies its options.
17
+ *
18
+ * Call `close()` on shutdown to close the subscriber connection it created.
19
+ */
20
+ export declare function bunRedisTransport(client: BunRedisClientLike, options?: {
21
+ subscriber?: BunRedisClientLike;
22
+ }): import("./transport.js").PubSubTransport & {
23
+ close(): void;
24
+ };
25
+ export {};
26
+ //# sourceMappingURL=redis-bun.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-bun.d.ts","sourceRoot":"","sources":["../../src/live/redis-bun.ts"],"names":[],"mappings":"AAEA,KAAK,QAAQ,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;AAE1D,2DAA2D;AAC3D,MAAM,WAAW,kBAAkB;IAClC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACvD,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAChE,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACnE,SAAS,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAAA;IACxC,KAAK,IAAI,IAAI,CAAA;IACb,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,IAAI,CAAA;IACzC,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,IAAI,CAAA;CACrD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAChC,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,kBAAkB,CAAA;CAAO;;EAkDjD"}