@providerprotocol/ai 0.0.35 → 0.0.36

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 (71) hide show
  1. package/README.md +17 -13
  2. package/dist/anthropic/index.d.ts +2 -2
  3. package/dist/anthropic/index.js +1 -1
  4. package/dist/cerebras/index.d.ts +2 -2
  5. package/dist/cerebras/index.js +1 -1
  6. package/dist/{chunk-7DXVRILR.js → chunk-2YXFLRQ6.js} +2 -2
  7. package/dist/chunk-2YXFLRQ6.js.map +1 -0
  8. package/dist/{chunk-HB4ZIH3T.js → chunk-4RX4VQCB.js} +2 -2
  9. package/dist/chunk-4RX4VQCB.js.map +1 -0
  10. package/dist/{chunk-ZI67WIQS.js → chunk-5IWHCXKN.js} +2 -2
  11. package/dist/chunk-5IWHCXKN.js.map +1 -0
  12. package/dist/{chunk-VOEWHQUB.js → chunk-CRP6Y7NF.js} +2 -2
  13. package/dist/chunk-CRP6Y7NF.js.map +1 -0
  14. package/dist/{chunk-3GWM5GR3.js → chunk-EPB3GQNL.js} +30 -65
  15. package/dist/chunk-EPB3GQNL.js.map +1 -0
  16. package/dist/{chunk-6S222DHN.js → chunk-RJGTRQ47.js} +20 -1
  17. package/dist/chunk-RJGTRQ47.js.map +1 -0
  18. package/dist/{embedding-CW6SaOOz.d.ts → embedding-BXA72PlJ.d.ts} +1 -1
  19. package/dist/google/index.d.ts +2 -2
  20. package/dist/google/index.js +1 -1
  21. package/dist/groq/index.d.ts +2 -2
  22. package/dist/groq/index.js +1 -1
  23. package/dist/http/index.d.ts +3 -3
  24. package/dist/{image-stream-C0ciACM2.d.ts → image-stream-CCgwB7ve.d.ts} +1 -1
  25. package/dist/index.d.ts +7 -7
  26. package/dist/index.js +1 -1
  27. package/dist/{llm-DwbUK7un.d.ts → llm-ByUFPcFH.d.ts} +1 -1
  28. package/dist/middleware/logging/index.d.ts +2 -2
  29. package/dist/middleware/parsed-object/index.d.ts +2 -2
  30. package/dist/middleware/parsed-object/index.js +1 -1
  31. package/dist/middleware/pubsub/index.d.ts +27 -34
  32. package/dist/middleware/pubsub/index.js +49 -119
  33. package/dist/middleware/pubsub/index.js.map +1 -1
  34. package/dist/middleware/pubsub/server/express/index.d.ts +24 -10
  35. package/dist/middleware/pubsub/server/express/index.js +2 -2
  36. package/dist/middleware/pubsub/server/fastify/index.d.ts +24 -10
  37. package/dist/middleware/pubsub/server/fastify/index.js +2 -2
  38. package/dist/middleware/pubsub/server/h3/index.d.ts +23 -9
  39. package/dist/middleware/pubsub/server/h3/index.js +2 -2
  40. package/dist/middleware/pubsub/server/index.d.ts +2 -2
  41. package/dist/middleware/pubsub/server/index.js +5 -5
  42. package/dist/middleware/pubsub/server/webapi/index.d.ts +23 -13
  43. package/dist/middleware/pubsub/server/webapi/index.js +2 -2
  44. package/dist/ollama/index.d.ts +2 -2
  45. package/dist/ollama/index.js +1 -1
  46. package/dist/openai/index.d.ts +2 -2
  47. package/dist/openai/index.js +1 -1
  48. package/dist/openrouter/index.d.ts +2 -2
  49. package/dist/openrouter/index.js +1 -1
  50. package/dist/proxy/index.d.ts +4 -4
  51. package/dist/proxy/index.js +1 -1
  52. package/dist/proxy/server/express/index.d.ts +4 -4
  53. package/dist/proxy/server/fastify/index.d.ts +4 -4
  54. package/dist/proxy/server/h3/index.d.ts +4 -4
  55. package/dist/proxy/server/index.d.ts +4 -4
  56. package/dist/proxy/server/webapi/index.d.ts +4 -4
  57. package/dist/responses/index.d.ts +2 -2
  58. package/dist/responses/index.js +1 -1
  59. package/dist/{retry-YayV42GV.d.ts → retry-BDMo4AVu.d.ts} +1 -1
  60. package/dist/{stream-CecfVCPO.d.ts → stream-S7nwQRqM.d.ts} +17 -6
  61. package/dist/types-CE4B7pno.d.ts +96 -0
  62. package/dist/xai/index.d.ts +2 -2
  63. package/dist/xai/index.js +1 -1
  64. package/package.json +1 -1
  65. package/dist/chunk-3GWM5GR3.js.map +0 -1
  66. package/dist/chunk-6S222DHN.js.map +0 -1
  67. package/dist/chunk-7DXVRILR.js.map +0 -1
  68. package/dist/chunk-HB4ZIH3T.js.map +0 -1
  69. package/dist/chunk-VOEWHQUB.js.map +0 -1
  70. package/dist/chunk-ZI67WIQS.js.map +0 -1
  71. package/dist/types-C8Gciizr.d.ts +0 -168
@@ -1,13 +1,13 @@
1
- import { M as Middleware } from '../../llm-DwbUK7un.js';
2
- import { M as MemoryAdapterOptions, P as PubSubAdapter, a as PubSubOptions } from '../../types-C8Gciizr.js';
3
- export { S as StoredStream, b as SubscriptionCallback, U as Unsubscribe } from '../../types-C8Gciizr.js';
4
- import '../../stream-CecfVCPO.js';
1
+ import { M as Middleware } from '../../llm-ByUFPcFH.js';
2
+ import { M as MemoryAdapterOptions, P as PubSubAdapter, a as PubSubOptions } from '../../types-CE4B7pno.js';
3
+ export { C as CompletionCallback, S as StoredStream, b as SubscriptionCallback, U as Unsubscribe } from '../../types-CE4B7pno.js';
4
+ import '../../stream-S7nwQRqM.js';
5
5
 
6
6
  /**
7
7
  * @fileoverview In-memory storage adapter for pub-sub middleware.
8
8
  *
9
- * Provides a simple Map-based implementation with LRU eviction
10
- * for temporary stream storage during active generation.
9
+ * Provides a simple Map-based implementation for temporary stream
10
+ * storage during active generation.
11
11
  *
12
12
  * @module middleware/pubsub/memory-adapter
13
13
  */
@@ -15,8 +15,8 @@ import '../../stream-CecfVCPO.js';
15
15
  /**
16
16
  * Creates an in-memory storage adapter for pub-sub middleware.
17
17
  *
18
- * Stores streams in a Map with LRU eviction when maxStreams is reached.
19
- * All methods return promises for interface compatibility with async backends.
18
+ * Stores streams in a Map. Throws when maxStreams is exceeded.
19
+ * Streams are created lazily on first append or subscribe.
20
20
  *
21
21
  * @param options - Adapter configuration
22
22
  * @returns A PubSubAdapter instance
@@ -25,9 +25,7 @@ import '../../stream-CecfVCPO.js';
25
25
  * ```typescript
26
26
  * import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';
27
27
  *
28
- * const mw = pubsubMiddleware({
29
- * adapter: memoryAdapter({ maxStreams: 500 }),
30
- * });
28
+ * const adapter = memoryAdapter({ maxStreams: 500 });
31
29
  * ```
32
30
  */
33
31
  declare function memoryAdapter(options?: MemoryAdapterOptions): PubSubAdapter;
@@ -64,9 +62,9 @@ declare function getAdapter(state: Map<string, unknown>): PubSubAdapter | undefi
64
62
  * - Creates stream entries for new requests
65
63
  * - Buffers all stream events
66
64
  * - Publishes events to subscribers
67
- * - Marks streams as completed
65
+ * - On stream end: notifies subscribers, then removes from adapter
68
66
  *
69
- * Server routes handle reconnection logic using `createSubscriberStream`.
67
+ * Server routes handle reconnection logic using `streamSubscriber`.
70
68
  *
71
69
  * @param options - Middleware configuration
72
70
  * @returns Middleware instance
@@ -75,28 +73,23 @@ declare function getAdapter(state: Map<string, unknown>): PubSubAdapter | undefi
75
73
  * ```typescript
76
74
  * import { llm } from '@providerprotocol/ai';
77
75
  * import { anthropic } from '@providerprotocol/ai/anthropic';
78
- * import { pubsubMiddleware } from '@providerprotocol/ai/middleware/pubsub';
79
- * import { createSubscriberStream } from '@providerprotocol/ai/middleware/pubsub/server/webapi';
80
- *
81
- * // Server route handling both new requests and reconnections
82
- * export async function POST(req: Request) {
83
- * const { messages, streamId } = await req.json();
84
- * const exists = await adapter.exists(streamId);
85
- *
86
- * if (!exists) {
87
- * // Start background generation (fire and forget)
88
- * const model = llm({
89
- * model: anthropic('claude-sonnet-4-20250514'),
90
- * middleware: [pubsubMiddleware({ adapter, streamId })],
91
- * });
92
- * consumeInBackground(model.stream(messages));
93
- * }
94
- *
95
- * // Both new and reconnect: subscribe to events
96
- * return new Response(createSubscriberStream(streamId, adapter), {
97
- * headers: { 'Content-Type': 'text/event-stream' },
76
+ * import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';
77
+ * import { h3 } from '@providerprotocol/ai/middleware/pubsub/server';
78
+ *
79
+ * const adapter = memoryAdapter();
80
+ *
81
+ * export default defineEventHandler(async (event) => {
82
+ * const { input, conversationId } = await readBody(event);
83
+ *
84
+ * // Fire and forget - subscriber connects immediately, events flow when ready
85
+ * const model = llm({
86
+ * model: anthropic('claude-sonnet-4-20250514'),
87
+ * middleware: [pubsubMiddleware({ adapter, streamId: conversationId })],
98
88
  * });
99
- * }
89
+ * model.stream(input).then(turn => saveToDatabase(turn));
90
+ *
91
+ * return h3.streamSubscriber(conversationId, adapter, event);
92
+ * });
100
93
  * ```
101
94
  */
102
95
  declare function pubsubMiddleware(options?: PubSubOptions): Middleware;
@@ -3,83 +3,51 @@ function memoryAdapter(options = {}) {
3
3
  const { maxStreams = 1e3 } = options;
4
4
  const streams = /* @__PURE__ */ new Map();
5
5
  const eventCursors = /* @__PURE__ */ new WeakMap();
6
- const evictOldest = () => {
7
- if (streams.size >= maxStreams) {
8
- let oldest = null;
9
- let oldestTime = Infinity;
10
- for (const [id, entry] of streams) {
11
- if (entry.stream.updatedAt < oldestTime) {
12
- oldestTime = entry.stream.updatedAt;
13
- oldest = id;
14
- }
6
+ const scheduleCallback = (callback) => {
7
+ queueMicrotask(() => {
8
+ try {
9
+ callback();
10
+ } catch {
15
11
  }
16
- if (oldest) {
17
- streams.delete(oldest);
12
+ });
13
+ };
14
+ const getOrCreate = (streamId) => {
15
+ let entry = streams.get(streamId);
16
+ if (!entry) {
17
+ if (streams.size >= maxStreams) {
18
+ throw new Error(`Maximum concurrent streams (${maxStreams}) exceeded`);
18
19
  }
20
+ entry = {
21
+ stream: {
22
+ streamId,
23
+ createdAt: Date.now(),
24
+ events: []
25
+ },
26
+ subscribers: /* @__PURE__ */ new Set()
27
+ };
28
+ streams.set(streamId, entry);
19
29
  }
30
+ return entry;
20
31
  };
21
32
  return {
22
33
  async exists(streamId) {
23
34
  return streams.has(streamId);
24
35
  },
25
- async create(streamId, metadata) {
26
- evictOldest();
27
- const now = Date.now();
28
- const stream = {
29
- streamId,
30
- modelId: metadata.modelId,
31
- provider: metadata.provider,
32
- createdAt: now,
33
- updatedAt: now,
34
- completed: false,
35
- events: []
36
- };
37
- streams.set(streamId, {
38
- stream,
39
- subscribers: /* @__PURE__ */ new Set()
40
- });
41
- },
42
36
  async append(streamId, event) {
43
- const entry = streams.get(streamId);
44
- if (!entry) {
45
- return;
46
- }
37
+ const entry = getOrCreate(streamId);
47
38
  entry.stream.events.push(event);
48
39
  eventCursors.set(event, entry.stream.events.length - 1);
49
- entry.stream.updatedAt = Date.now();
50
- },
51
- async markCompleted(streamId) {
52
- const entry = streams.get(streamId);
53
- if (!entry) {
54
- return;
55
- }
56
- entry.stream.completed = true;
57
- entry.stream.updatedAt = Date.now();
58
- },
59
- async isCompleted(streamId) {
60
- const entry = streams.get(streamId);
61
- return entry?.stream.completed ?? false;
62
40
  },
63
41
  async getEvents(streamId) {
64
42
  const entry = streams.get(streamId);
65
- if (!entry) {
66
- return null;
67
- }
68
- return [...entry.stream.events];
69
- },
70
- async getStream(streamId) {
71
- const entry = streams.get(streamId);
72
- return entry?.stream ?? null;
43
+ return entry ? [...entry.stream.events] : [];
73
44
  },
74
- subscribe(streamId, callback) {
75
- const entry = streams.get(streamId);
76
- if (!entry) {
77
- return () => {
78
- };
79
- }
80
- entry.subscribers.add(callback);
45
+ subscribe(streamId, onEvent, onComplete) {
46
+ const entry = getOrCreate(streamId);
47
+ const subscriber = { onEvent, onComplete };
48
+ entry.subscribers.add(subscriber);
81
49
  return () => {
82
- entry.subscribers.delete(callback);
50
+ entry.subscribers.delete(subscriber);
83
51
  };
84
52
  },
85
53
  publish(streamId, event) {
@@ -88,23 +56,19 @@ function memoryAdapter(options = {}) {
88
56
  return;
89
57
  }
90
58
  const cursor = eventCursors.get(event) ?? entry.stream.events.length - 1;
91
- for (const callback of entry.subscribers) {
92
- try {
93
- callback(event, cursor);
94
- } catch {
95
- }
59
+ for (const subscriber of entry.subscribers) {
60
+ scheduleCallback(() => {
61
+ subscriber.onEvent(event, cursor);
62
+ });
96
63
  }
97
64
  },
98
65
  async remove(streamId) {
99
- streams.delete(streamId);
100
- },
101
- async cleanup(maxAge) {
102
- const now = Date.now();
103
- const cutoff = now - maxAge;
104
- for (const [id, entry] of streams) {
105
- if (entry.stream.updatedAt < cutoff) {
106
- streams.delete(id);
66
+ const entry = streams.get(streamId);
67
+ if (entry) {
68
+ for (const subscriber of entry.subscribers) {
69
+ scheduleCallback(subscriber.onComplete);
107
70
  }
71
+ streams.delete(streamId);
108
72
  }
109
73
  }
110
74
  };
@@ -113,9 +77,6 @@ function memoryAdapter(options = {}) {
113
77
  // src/middleware/pubsub/index.ts
114
78
  var STATE_KEY_STREAM_ID = "pubsub:streamId";
115
79
  var STATE_KEY_ADAPTER = "pubsub:adapter";
116
- var DEFAULT_TTL = 6e5;
117
- var CLEANUP_INTERVAL = 6e4;
118
- var adapterCleanupTimes = /* @__PURE__ */ new WeakMap();
119
80
  function getStreamId(state) {
120
81
  return state.get(STATE_KEY_STREAM_ID);
121
82
  }
@@ -125,8 +86,7 @@ function getAdapter(state) {
125
86
  function pubsubMiddleware(options = {}) {
126
87
  const {
127
88
  adapter = memoryAdapter(),
128
- streamId,
129
- ttl = DEFAULT_TTL
89
+ streamId
130
90
  } = options;
131
91
  const appendChains = /* @__PURE__ */ new Map();
132
92
  const enqueueAppend = (id, event) => {
@@ -151,25 +111,15 @@ function pubsubMiddleware(options = {}) {
151
111
  const clearAppendState = (id) => {
152
112
  appendChains.delete(id);
153
113
  };
154
- const maybeCleanup = () => {
155
- const now = Date.now();
156
- const lastCleanup = adapterCleanupTimes.get(adapter) ?? 0;
157
- if (now - lastCleanup > CLEANUP_INTERVAL) {
158
- adapterCleanupTimes.set(adapter, now);
159
- adapter.cleanup(ttl).catch(() => {
160
- });
161
- }
162
- };
163
- const finalizeStream = async (ctx) => {
164
- const id = ctx.state.get(STATE_KEY_STREAM_ID);
114
+ const finalizeStreamByState = async (state) => {
115
+ const id = state.get(STATE_KEY_STREAM_ID);
165
116
  if (!id) {
166
117
  return;
167
118
  }
168
119
  await waitForAppends(id);
169
- await adapter.markCompleted(id).catch(() => {
170
- });
171
120
  clearAppendState(id);
172
- maybeCleanup();
121
+ await adapter.remove(id).catch(() => {
122
+ });
173
123
  };
174
124
  return {
175
125
  name: "pubsub",
@@ -177,21 +127,9 @@ function pubsubMiddleware(options = {}) {
177
127
  ctx.state.set(STATE_KEY_ADAPTER, adapter);
178
128
  if (streamId) {
179
129
  ctx.state.set(STATE_KEY_STREAM_ID, streamId);
180
- }
181
- },
182
- async onRequest(ctx) {
183
- if (!streamId) {
184
- return;
185
- }
186
- if (!ctx.streaming) {
187
- return;
188
- }
189
- const exists = await adapter.exists(streamId);
190
- if (!exists) {
191
- await adapter.create(streamId, {
192
- modelId: ctx.modelId,
193
- provider: ctx.provider
194
- });
130
+ adapter.subscribe(streamId, () => {
131
+ }, () => {
132
+ })();
195
133
  }
196
134
  },
197
135
  onStreamEvent(event, ctx) {
@@ -203,21 +141,13 @@ function pubsubMiddleware(options = {}) {
203
141
  return event;
204
142
  },
205
143
  async onStreamEnd(ctx) {
206
- const id = ctx.state.get(STATE_KEY_STREAM_ID);
207
- if (!id) {
208
- return;
209
- }
210
- await waitForAppends(id);
211
- await adapter.markCompleted(id).catch(() => {
212
- });
213
- clearAppendState(id);
214
- maybeCleanup();
144
+ await finalizeStreamByState(ctx.state);
215
145
  },
216
146
  async onError(_error, ctx) {
217
- await finalizeStream(ctx);
147
+ await finalizeStreamByState(ctx.state);
218
148
  },
219
149
  async onAbort(_error, ctx) {
220
- await finalizeStream(ctx);
150
+ await finalizeStreamByState(ctx.state);
221
151
  }
222
152
  };
223
153
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/middleware/pubsub/memory-adapter.ts","../../../src/middleware/pubsub/index.ts"],"sourcesContent":["/**\n * @fileoverview In-memory storage adapter for pub-sub middleware.\n *\n * Provides a simple Map-based implementation with LRU eviction\n * for temporary stream storage during active generation.\n *\n * @module middleware/pubsub/memory-adapter\n */\n\nimport type { StreamEvent } from '../../types/stream.ts';\nimport type {\n PubSubAdapter,\n StoredStream,\n SubscriptionCallback,\n Unsubscribe,\n MemoryAdapterOptions,\n} from './types.ts';\n\n/**\n * Internal mutable version of StoredStream for adapter operations.\n * Public API exposes readonly StoredStream.\n */\ninterface MutableStoredStream {\n streamId: string;\n modelId: string;\n provider: string;\n createdAt: number;\n updatedAt: number;\n completed: boolean;\n events: StreamEvent[];\n}\n\ninterface StreamEntry {\n stream: MutableStoredStream;\n subscribers: Set<SubscriptionCallback>;\n}\n\n/**\n * Creates an in-memory storage adapter for pub-sub middleware.\n *\n * Stores streams in a Map with LRU eviction when maxStreams is reached.\n * All methods return promises for interface compatibility with async backends.\n *\n * @param options - Adapter configuration\n * @returns A PubSubAdapter instance\n *\n * @example\n * ```typescript\n * import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';\n *\n * const mw = pubsubMiddleware({\n * adapter: memoryAdapter({ maxStreams: 500 }),\n * });\n * ```\n */\nexport function memoryAdapter(options: MemoryAdapterOptions = {}): PubSubAdapter {\n const { maxStreams = 1000 } = options;\n\n const streams = new Map<string, StreamEntry>();\n const eventCursors = new WeakMap<StreamEvent, number>();\n\n const evictOldest = (): void => {\n if (streams.size >= maxStreams) {\n let oldest: string | null = null;\n let oldestTime = Infinity;\n\n for (const [id, entry] of streams) {\n if (entry.stream.updatedAt < oldestTime) {\n oldestTime = entry.stream.updatedAt;\n oldest = id;\n }\n }\n\n if (oldest) {\n streams.delete(oldest);\n }\n }\n };\n\n return {\n async exists(streamId): Promise<boolean> {\n return streams.has(streamId);\n },\n\n async create(streamId, metadata): Promise<void> {\n evictOldest();\n\n const now = Date.now();\n const stream: MutableStoredStream = {\n streamId,\n modelId: metadata.modelId,\n provider: metadata.provider,\n createdAt: now,\n updatedAt: now,\n completed: false,\n events: [],\n };\n\n streams.set(streamId, {\n stream,\n subscribers: new Set(),\n });\n },\n\n async append(streamId, event): Promise<void> {\n const entry = streams.get(streamId);\n if (!entry) {\n return;\n }\n\n entry.stream.events.push(event);\n eventCursors.set(event, entry.stream.events.length - 1);\n entry.stream.updatedAt = Date.now();\n },\n\n async markCompleted(streamId): Promise<void> {\n const entry = streams.get(streamId);\n if (!entry) {\n return;\n }\n\n entry.stream.completed = true;\n entry.stream.updatedAt = Date.now();\n },\n\n async isCompleted(streamId): Promise<boolean> {\n const entry = streams.get(streamId);\n return entry?.stream.completed ?? false;\n },\n\n async getEvents(streamId): Promise<StreamEvent[] | null> {\n const entry = streams.get(streamId);\n if (!entry) {\n return null;\n }\n\n return [...entry.stream.events];\n },\n\n async getStream(streamId): Promise<StoredStream | null> {\n const entry = streams.get(streamId);\n return entry?.stream ?? null;\n },\n\n subscribe(streamId, callback): Unsubscribe {\n const entry = streams.get(streamId);\n if (!entry) {\n return () => {};\n }\n\n entry.subscribers.add(callback);\n\n return () => {\n entry.subscribers.delete(callback);\n };\n },\n\n publish(streamId, event): void {\n const entry = streams.get(streamId);\n if (!entry) {\n return;\n }\n\n const cursor = eventCursors.get(event) ?? entry.stream.events.length - 1;\n for (const callback of entry.subscribers) {\n try {\n callback(event, cursor);\n } catch {\n // Subscriber errors should not affect other subscribers\n }\n }\n },\n\n async remove(streamId): Promise<void> {\n streams.delete(streamId);\n },\n\n async cleanup(maxAge): Promise<void> {\n const now = Date.now();\n const cutoff = now - maxAge;\n\n for (const [id, entry] of streams) {\n if (entry.stream.updatedAt < cutoff) {\n streams.delete(id);\n }\n }\n },\n };\n}\n","/**\n * @fileoverview Pub-sub middleware for stream resumption.\n *\n * Enables reconnecting clients to catch up on missed events during\n * active generation. The middleware buffers events and publishes them\n * to subscribers. Server routes handle reconnection logic using the\n * exported `createSubscriberStream` utility.\n *\n * @module middleware/pubsub\n */\n\nimport type {\n Middleware,\n MiddlewareContext,\n StreamContext,\n} from '../../types/middleware.ts';\nimport type { StreamEvent } from '../../types/stream.ts';\nimport type { PubSubAdapter, PubSubOptions } from './types.ts';\nimport { memoryAdapter } from './memory-adapter.ts';\n\nexport type {\n PubSubAdapter,\n PubSubOptions,\n StoredStream,\n SubscriptionCallback,\n Unsubscribe,\n MemoryAdapterOptions,\n} from './types.ts';\nexport { memoryAdapter } from './memory-adapter.ts';\n\nconst STATE_KEY_STREAM_ID = 'pubsub:streamId';\nconst STATE_KEY_ADAPTER = 'pubsub:adapter';\n\nconst DEFAULT_TTL = 600_000; // 10 minutes\nconst CLEANUP_INTERVAL = 60_000; // 1 minute\n\n/** Track last cleanup time per adapter to avoid shared state issues */\nconst adapterCleanupTimes = new WeakMap<PubSubAdapter, number>();\n\ninterface AppendChainState {\n chain: Promise<void>;\n}\n\n/**\n * Gets the stream ID from middleware state.\n *\n * @param state - Middleware state map\n * @returns Stream ID or undefined if not set\n */\nexport function getStreamId(state: Map<string, unknown>): string | undefined {\n return state.get(STATE_KEY_STREAM_ID) as string | undefined;\n}\n\n/**\n * Gets the adapter from middleware state.\n *\n * @param state - Middleware state map\n * @returns Adapter or undefined if not set\n */\nexport function getAdapter(state: Map<string, unknown>): PubSubAdapter | undefined {\n return state.get(STATE_KEY_ADAPTER) as PubSubAdapter | undefined;\n}\n\n/**\n * Creates pub-sub middleware for stream buffering and publishing.\n *\n * The middleware:\n * - Creates stream entries for new requests\n * - Buffers all stream events\n * - Publishes events to subscribers\n * - Marks streams as completed\n *\n * Server routes handle reconnection logic using `createSubscriberStream`.\n *\n * @param options - Middleware configuration\n * @returns Middleware instance\n *\n * @example\n * ```typescript\n * import { llm } from '@providerprotocol/ai';\n * import { anthropic } from '@providerprotocol/ai/anthropic';\n * import { pubsubMiddleware } from '@providerprotocol/ai/middleware/pubsub';\n * import { createSubscriberStream } from '@providerprotocol/ai/middleware/pubsub/server/webapi';\n *\n * // Server route handling both new requests and reconnections\n * export async function POST(req: Request) {\n * const { messages, streamId } = await req.json();\n * const exists = await adapter.exists(streamId);\n *\n * if (!exists) {\n * // Start background generation (fire and forget)\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * middleware: [pubsubMiddleware({ adapter, streamId })],\n * });\n * consumeInBackground(model.stream(messages));\n * }\n *\n * // Both new and reconnect: subscribe to events\n * return new Response(createSubscriberStream(streamId, adapter), {\n * headers: { 'Content-Type': 'text/event-stream' },\n * });\n * }\n * ```\n */\nexport function pubsubMiddleware(options: PubSubOptions = {}): Middleware {\n const {\n adapter = memoryAdapter(),\n streamId,\n ttl = DEFAULT_TTL,\n } = options;\n\n const appendChains = new Map<string, AppendChainState>();\n\n const enqueueAppend = (id: string, event: StreamEvent): void => {\n const state = appendChains.get(id) ?? { chain: Promise.resolve() };\n\n const task = state.chain\n .catch(() => {})\n .then(async () => {\n await adapter.append(id, event);\n adapter.publish(id, event);\n });\n\n state.chain = task.catch(() => {});\n appendChains.set(id, state);\n };\n\n const waitForAppends = async (id: string): Promise<void> => {\n const state = appendChains.get(id);\n if (!state) {\n return;\n }\n\n await state.chain.catch(() => {});\n };\n\n const clearAppendState = (id: string): void => {\n appendChains.delete(id);\n };\n\n const maybeCleanup = (): void => {\n const now = Date.now();\n const lastCleanup = adapterCleanupTimes.get(adapter) ?? 0;\n if (now - lastCleanup > CLEANUP_INTERVAL) {\n adapterCleanupTimes.set(adapter, now);\n adapter.cleanup(ttl).catch(() => {\n // Cleanup errors are non-fatal\n });\n }\n };\n\n const finalizeStream = async (ctx: MiddlewareContext): Promise<void> => {\n const id = ctx.state.get(STATE_KEY_STREAM_ID) as string | undefined;\n if (!id) {\n return;\n }\n\n await waitForAppends(id);\n\n await adapter.markCompleted(id).catch(() => {\n // Completion errors are non-fatal\n });\n\n clearAppendState(id);\n\n maybeCleanup();\n };\n\n return {\n name: 'pubsub',\n\n onStart(ctx: MiddlewareContext): void {\n ctx.state.set(STATE_KEY_ADAPTER, adapter);\n\n if (streamId) {\n ctx.state.set(STATE_KEY_STREAM_ID, streamId);\n }\n },\n\n async onRequest(ctx: MiddlewareContext): Promise<void> {\n if (!streamId) {\n return;\n }\n if (!ctx.streaming) {\n return;\n }\n\n // Create stream entry if it doesn't exist\n const exists = await adapter.exists(streamId);\n if (!exists) {\n await adapter.create(streamId, {\n modelId: ctx.modelId,\n provider: ctx.provider,\n });\n }\n },\n\n onStreamEvent(event: StreamEvent, ctx: StreamContext): StreamEvent {\n const id = ctx.state.get(STATE_KEY_STREAM_ID) as string | undefined;\n if (!id) {\n return event;\n }\n\n // Buffer first, then broadcast - ensures event is persisted before\n // subscribers are notified, preventing replay gaps with async adapters\n enqueueAppend(id, event);\n\n return event;\n },\n\n async onStreamEnd(ctx: StreamContext): Promise<void> {\n const id = ctx.state.get(STATE_KEY_STREAM_ID) as string | undefined;\n if (!id) {\n return;\n }\n\n await waitForAppends(id);\n\n await adapter.markCompleted(id).catch(() => {\n // Completion errors are non-fatal\n });\n\n clearAppendState(id);\n\n maybeCleanup();\n },\n\n async onError(_error: Error, ctx: MiddlewareContext): Promise<void> {\n await finalizeStream(ctx);\n },\n\n async onAbort(_error: Error, ctx: MiddlewareContext): Promise<void> {\n await finalizeStream(ctx);\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/middleware/pubsub/memory-adapter.ts","../../../src/middleware/pubsub/index.ts"],"sourcesContent":["/**\n * @fileoverview In-memory storage adapter for pub-sub middleware.\n *\n * Provides a simple Map-based implementation for temporary stream\n * storage during active generation.\n *\n * @module middleware/pubsub/memory-adapter\n */\n\nimport type { StreamEvent } from '../../types/stream.ts';\nimport type {\n PubSubAdapter,\n StoredStream,\n SubscriptionCallback,\n CompletionCallback,\n Unsubscribe,\n MemoryAdapterOptions,\n} from './types.ts';\n\ninterface MutableStoredStream {\n streamId: string;\n createdAt: number;\n events: StreamEvent[];\n}\n\ninterface Subscriber {\n onEvent: SubscriptionCallback;\n onComplete: CompletionCallback;\n}\n\ninterface StreamEntry {\n stream: MutableStoredStream;\n subscribers: Set<Subscriber>;\n}\n\n/**\n * Creates an in-memory storage adapter for pub-sub middleware.\n *\n * Stores streams in a Map. Throws when maxStreams is exceeded.\n * Streams are created lazily on first append or subscribe.\n *\n * @param options - Adapter configuration\n * @returns A PubSubAdapter instance\n *\n * @example\n * ```typescript\n * import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';\n *\n * const adapter = memoryAdapter({ maxStreams: 500 });\n * ```\n */\nexport function memoryAdapter(options: MemoryAdapterOptions = {}): PubSubAdapter {\n const { maxStreams = 1000 } = options;\n\n const streams = new Map<string, StreamEntry>();\n const eventCursors = new WeakMap<StreamEvent, number>();\n\n const scheduleCallback = (callback: () => void): void => {\n queueMicrotask(() => {\n try {\n callback();\n } catch {\n // Subscriber errors should not affect other subscribers\n }\n });\n };\n\n const getOrCreate = (streamId: string): StreamEntry => {\n let entry = streams.get(streamId);\n if (!entry) {\n if (streams.size >= maxStreams) {\n throw new Error(`Maximum concurrent streams (${maxStreams}) exceeded`);\n }\n entry = {\n stream: {\n streamId,\n createdAt: Date.now(),\n events: [],\n },\n subscribers: new Set(),\n };\n streams.set(streamId, entry);\n }\n return entry;\n };\n\n return {\n async exists(streamId): Promise<boolean> {\n return streams.has(streamId);\n },\n\n async append(streamId, event): Promise<void> {\n const entry = getOrCreate(streamId);\n entry.stream.events.push(event);\n eventCursors.set(event, entry.stream.events.length - 1);\n },\n\n async getEvents(streamId): Promise<StreamEvent[]> {\n const entry = streams.get(streamId);\n return entry ? [...entry.stream.events] : [];\n },\n\n subscribe(streamId, onEvent, onComplete): Unsubscribe {\n const entry = getOrCreate(streamId);\n const subscriber: Subscriber = { onEvent, onComplete };\n entry.subscribers.add(subscriber);\n\n return () => {\n entry.subscribers.delete(subscriber);\n };\n },\n\n publish(streamId, event): void {\n const entry = streams.get(streamId);\n if (!entry) {\n return;\n }\n\n const cursor = eventCursors.get(event) ?? entry.stream.events.length - 1;\n for (const subscriber of entry.subscribers) {\n scheduleCallback(() => {\n subscriber.onEvent(event, cursor);\n });\n }\n },\n\n async remove(streamId): Promise<void> {\n const entry = streams.get(streamId);\n if (entry) {\n for (const subscriber of entry.subscribers) {\n scheduleCallback(subscriber.onComplete);\n }\n streams.delete(streamId);\n }\n },\n };\n}\n","/**\n * @fileoverview Pub-sub middleware for stream resumption.\n *\n * Enables reconnecting clients to catch up on missed events during\n * active generation. The middleware buffers events and publishes them\n * to subscribers. Server routes handle reconnection logic using the\n * exported `createSubscriberStream` utility.\n *\n * @module middleware/pubsub\n */\n\nimport type {\n Middleware,\n MiddlewareContext,\n StreamContext,\n} from '../../types/middleware.ts';\nimport type { StreamEvent } from '../../types/stream.ts';\nimport type { PubSubAdapter, PubSubOptions } from './types.ts';\nimport { memoryAdapter } from './memory-adapter.ts';\n\nexport type {\n PubSubAdapter,\n PubSubOptions,\n StoredStream,\n SubscriptionCallback,\n CompletionCallback,\n Unsubscribe,\n MemoryAdapterOptions,\n} from './types.ts';\nexport { memoryAdapter } from './memory-adapter.ts';\n\nconst STATE_KEY_STREAM_ID = 'pubsub:streamId';\nconst STATE_KEY_ADAPTER = 'pubsub:adapter';\n\ninterface AppendChainState {\n chain: Promise<void>;\n}\n\n/**\n * Gets the stream ID from middleware state.\n *\n * @param state - Middleware state map\n * @returns Stream ID or undefined if not set\n */\nexport function getStreamId(state: Map<string, unknown>): string | undefined {\n return state.get(STATE_KEY_STREAM_ID) as string | undefined;\n}\n\n/**\n * Gets the adapter from middleware state.\n *\n * @param state - Middleware state map\n * @returns Adapter or undefined if not set\n */\nexport function getAdapter(state: Map<string, unknown>): PubSubAdapter | undefined {\n return state.get(STATE_KEY_ADAPTER) as PubSubAdapter | undefined;\n}\n\n/**\n * Creates pub-sub middleware for stream buffering and publishing.\n *\n * The middleware:\n * - Creates stream entries for new requests\n * - Buffers all stream events\n * - Publishes events to subscribers\n * - On stream end: notifies subscribers, then removes from adapter\n *\n * Server routes handle reconnection logic using `streamSubscriber`.\n *\n * @param options - Middleware configuration\n * @returns Middleware instance\n *\n * @example\n * ```typescript\n * import { llm } from '@providerprotocol/ai';\n * import { anthropic } from '@providerprotocol/ai/anthropic';\n * import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';\n * import { h3 } from '@providerprotocol/ai/middleware/pubsub/server';\n *\n * const adapter = memoryAdapter();\n *\n * export default defineEventHandler(async (event) => {\n * const { input, conversationId } = await readBody(event);\n *\n * // Fire and forget - subscriber connects immediately, events flow when ready\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * middleware: [pubsubMiddleware({ adapter, streamId: conversationId })],\n * });\n * model.stream(input).then(turn => saveToDatabase(turn));\n *\n * return h3.streamSubscriber(conversationId, adapter, event);\n * });\n * ```\n */\nexport function pubsubMiddleware(options: PubSubOptions = {}): Middleware {\n const {\n adapter = memoryAdapter(),\n streamId,\n } = options;\n\n const appendChains = new Map<string, AppendChainState>();\n\n const enqueueAppend = (id: string, event: StreamEvent): void => {\n const state = appendChains.get(id) ?? { chain: Promise.resolve() };\n\n const task = state.chain\n .catch(() => {})\n .then(async () => {\n await adapter.append(id, event);\n adapter.publish(id, event);\n });\n\n state.chain = task.catch(() => {});\n appendChains.set(id, state);\n };\n\n const waitForAppends = async (id: string): Promise<void> => {\n const state = appendChains.get(id);\n if (!state) {\n return;\n }\n\n await state.chain.catch(() => {});\n };\n\n const clearAppendState = (id: string): void => {\n appendChains.delete(id);\n };\n\n /**\n * Finalizes a stream by marking completion and removing from adapter.\n *\n * Called on any terminal state (complete, error, abort). After finalization,\n * the stream is removed from the adapter. Apps should use `.then()` to persist\n * completed conversations and serve them from their own storage on reconnect.\n */\n const finalizeStreamByState = async (state: Map<string, unknown>): Promise<void> => {\n const id = state.get(STATE_KEY_STREAM_ID) as string | undefined;\n if (!id) {\n return;\n }\n\n await waitForAppends(id);\n clearAppendState(id);\n\n // Remove from adapter (notifies subscribers) - apps persist via .then()\n await adapter.remove(id).catch(() => {});\n };\n\n return {\n name: 'pubsub',\n\n onStart(ctx: MiddlewareContext): void {\n ctx.state.set(STATE_KEY_ADAPTER, adapter);\n\n if (streamId) {\n ctx.state.set(STATE_KEY_STREAM_ID, streamId);\n // Ensure stream exists immediately so exists() returns true\n // before first token arrives (prevents duplicate generations)\n adapter.subscribe(streamId, () => {}, () => {})();\n }\n },\n\n onStreamEvent(event: StreamEvent, ctx: StreamContext): StreamEvent {\n const id = ctx.state.get(STATE_KEY_STREAM_ID) as string | undefined;\n if (!id) {\n return event;\n }\n\n enqueueAppend(id, event);\n\n return event;\n },\n\n async onStreamEnd(ctx: StreamContext): Promise<void> {\n await finalizeStreamByState(ctx.state);\n },\n\n async onError(_error: Error, ctx: MiddlewareContext): Promise<void> {\n await finalizeStreamByState(ctx.state);\n },\n\n async onAbort(_error: Error, ctx: MiddlewareContext): Promise<void> {\n await finalizeStreamByState(ctx.state);\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,5 @@
1
- import { P as PubSubAdapter } from '../../../../types-C8Gciizr.js';
2
- import '../../../../stream-CecfVCPO.js';
1
+ import { P as PubSubAdapter } from '../../../../types-CE4B7pno.js';
2
+ import '../../../../stream-S7nwQRqM.js';
3
3
 
4
4
  /**
5
5
  * @fileoverview Express/Connect adapter for pub-sub stream resumption.
@@ -22,10 +22,10 @@ interface ExpressResponse {
22
22
  /**
23
23
  * Stream buffered and live events to an Express response.
24
24
  *
25
- * This utility handles the reconnection pattern for Express routes:
26
- * 1. Replays all buffered events from the adapter
27
- * 2. If stream is already completed, ends immediately
28
- * 3. Otherwise, subscribes to live events until completion
25
+ * Handles reconnection for Express routes:
26
+ * 1. Replays buffered events from the adapter
27
+ * 2. Subscribes to live events until completion signal
28
+ * 3. Ends when stream completes or client disconnects
29
29
  *
30
30
  * @param streamId - The stream ID to subscribe to
31
31
  * @param adapter - The pub-sub adapter instance
@@ -33,11 +33,25 @@ interface ExpressResponse {
33
33
  *
34
34
  * @example
35
35
  * ```typescript
36
- * import { streamSubscriber } from '@providerprotocol/ai/middleware/pubsub/server/express';
36
+ * import { llm } from '@providerprotocol/ai';
37
+ * import { anthropic } from '@providerprotocol/ai/anthropic';
38
+ * import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';
39
+ * import { express } from '@providerprotocol/ai/middleware/pubsub/server';
37
40
  *
38
- * app.post('/api/ai/reconnect', async (req, res) => {
39
- * const { streamId } = req.body;
40
- * streamSubscriber(streamId, adapter, res);
41
+ * const adapter = memoryAdapter();
42
+ *
43
+ * app.post('/api/chat', async (req, res) => {
44
+ * const { input, conversationId } = req.body;
45
+ *
46
+ * if (!await adapter.exists(conversationId)) {
47
+ * const model = llm({
48
+ * model: anthropic('claude-sonnet-4-20250514'),
49
+ * middleware: [pubsubMiddleware({ adapter, streamId: conversationId })],
50
+ * });
51
+ * model.stream(input).then(turn => saveToDatabase(conversationId, turn));
52
+ * }
53
+ *
54
+ * return express.streamSubscriber(conversationId, adapter, res);
41
55
  * });
42
56
  * ```
43
57
  */
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  express,
3
3
  streamSubscriber
4
- } from "../../../../chunk-ZI67WIQS.js";
5
- import "../../../../chunk-3GWM5GR3.js";
4
+ } from "../../../../chunk-5IWHCXKN.js";
5
+ import "../../../../chunk-EPB3GQNL.js";
6
6
  import "../../../../chunk-ETBFOLQN.js";
7
7
  export {
8
8
  express,
@@ -1,5 +1,5 @@
1
- import { P as PubSubAdapter } from '../../../../types-C8Gciizr.js';
2
- import '../../../../stream-CecfVCPO.js';
1
+ import { P as PubSubAdapter } from '../../../../types-CE4B7pno.js';
2
+ import '../../../../stream-S7nwQRqM.js';
3
3
 
4
4
  /**
5
5
  * @fileoverview Fastify adapter for pub-sub stream resumption.
@@ -23,10 +23,10 @@ interface FastifyReply {
23
23
  /**
24
24
  * Stream buffered and live events to a Fastify reply.
25
25
  *
26
- * This utility handles the reconnection pattern for Fastify routes:
27
- * 1. Replays all buffered events from the adapter
28
- * 2. If stream is already completed, ends immediately
29
- * 3. Otherwise, subscribes to live events until completion
26
+ * Handles reconnection for Fastify routes:
27
+ * 1. Replays buffered events from the adapter
28
+ * 2. Subscribes to live events until completion signal
29
+ * 3. Ends when stream completes or client disconnects
30
30
  *
31
31
  * @param streamId - The stream ID to subscribe to
32
32
  * @param adapter - The pub-sub adapter instance
@@ -34,11 +34,25 @@ interface FastifyReply {
34
34
  *
35
35
  * @example
36
36
  * ```typescript
37
- * import { streamSubscriber } from '@providerprotocol/ai/middleware/pubsub/server/fastify';
37
+ * import { llm } from '@providerprotocol/ai';
38
+ * import { anthropic } from '@providerprotocol/ai/anthropic';
39
+ * import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';
40
+ * import { fastify as pubsubFastify } from '@providerprotocol/ai/middleware/pubsub/server';
38
41
  *
39
- * app.post('/api/ai/reconnect', async (request, reply) => {
40
- * const { streamId } = request.body;
41
- * return streamSubscriber(streamId, adapter, reply);
42
+ * const adapter = memoryAdapter();
43
+ *
44
+ * app.post('/api/chat', async (request, reply) => {
45
+ * const { input, conversationId } = request.body as { input: string; conversationId: string };
46
+ *
47
+ * if (!await adapter.exists(conversationId)) {
48
+ * const model = llm({
49
+ * model: anthropic('claude-sonnet-4-20250514'),
50
+ * middleware: [pubsubMiddleware({ adapter, streamId: conversationId })],
51
+ * });
52
+ * model.stream(input).then(turn => saveToDatabase(conversationId, turn));
53
+ * }
54
+ *
55
+ * return pubsubFastify.streamSubscriber(conversationId, adapter, reply);
42
56
  * });
43
57
  * ```
44
58
  */
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  fastify,
3
3
  streamSubscriber
4
- } from "../../../../chunk-VOEWHQUB.js";
5
- import "../../../../chunk-3GWM5GR3.js";
4
+ } from "../../../../chunk-CRP6Y7NF.js";
5
+ import "../../../../chunk-EPB3GQNL.js";
6
6
  import "../../../../chunk-ETBFOLQN.js";
7
7
  export {
8
8
  fastify,
@@ -1,5 +1,5 @@
1
- import { P as PubSubAdapter } from '../../../../types-C8Gciizr.js';
2
- import '../../../../stream-CecfVCPO.js';
1
+ import { P as PubSubAdapter } from '../../../../types-CE4B7pno.js';
2
+ import '../../../../stream-S7nwQRqM.js';
3
3
 
4
4
  /**
5
5
  * @fileoverview H3/Nitro/Nuxt adapter for pub-sub stream resumption.
@@ -26,10 +26,10 @@ interface H3Event {
26
26
  /**
27
27
  * Stream buffered and live events to an H3 event response.
28
28
  *
29
- * This utility handles the reconnection pattern for H3/Nuxt routes:
30
- * 1. Replays all buffered events from the adapter
31
- * 2. If stream is already completed, ends immediately
32
- * 3. Otherwise, subscribes to live events until completion
29
+ * Handles reconnection for H3/Nuxt routes:
30
+ * 1. Replays buffered events from the adapter
31
+ * 2. Subscribes to live events until completion signal
32
+ * 3. Ends when stream completes or client disconnects
33
33
  *
34
34
  * @param streamId - The stream ID to subscribe to
35
35
  * @param adapter - The pub-sub adapter instance
@@ -37,11 +37,25 @@ interface H3Event {
37
37
  *
38
38
  * @example
39
39
  * ```typescript
40
- * import { streamSubscriber } from '@providerprotocol/ai/middleware/pubsub/server/h3';
40
+ * import { llm } from '@providerprotocol/ai';
41
+ * import { anthropic } from '@providerprotocol/ai/anthropic';
42
+ * import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';
43
+ * import { h3 } from '@providerprotocol/ai/middleware/pubsub/server';
44
+ *
45
+ * const adapter = memoryAdapter();
41
46
  *
42
47
  * export default defineEventHandler(async (event) => {
43
- * const { streamId } = await readBody(event);
44
- * return streamSubscriber(streamId, adapter, event);
48
+ * const { input, conversationId } = await readBody(event);
49
+ *
50
+ * if (!await adapter.exists(conversationId)) {
51
+ * const model = llm({
52
+ * model: anthropic('claude-sonnet-4-20250514'),
53
+ * middleware: [pubsubMiddleware({ adapter, streamId: conversationId })],
54
+ * });
55
+ * model.stream(input).then(turn => saveToDatabase(conversationId, turn));
56
+ * }
57
+ *
58
+ * return h3.streamSubscriber(conversationId, adapter, event);
45
59
  * });
46
60
  * ```
47
61
  */
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  h3,
3
3
  streamSubscriber
4
- } from "../../../../chunk-HB4ZIH3T.js";
5
- import "../../../../chunk-3GWM5GR3.js";
4
+ } from "../../../../chunk-4RX4VQCB.js";
5
+ import "../../../../chunk-EPB3GQNL.js";
6
6
  import "../../../../chunk-ETBFOLQN.js";
7
7
  export {
8
8
  h3,
@@ -6,8 +6,8 @@ import { streamSubscriber } from './express/index.js';
6
6
  export { express } from './express/index.js';
7
7
  import { createSubscriberStream } from './webapi/index.js';
8
8
  export { webapi } from './webapi/index.js';
9
- export { P as PubSubAdapter } from '../../../types-C8Gciizr.js';
10
- import '../../../stream-CecfVCPO.js';
9
+ export { P as PubSubAdapter } from '../../../types-CE4B7pno.js';
10
+ import '../../../stream-S7nwQRqM.js';
11
11
 
12
12
  /**
13
13
  * Server adapters namespace for pub-sub stream resumption.
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  webapi
3
- } from "../../../chunk-7DXVRILR.js";
3
+ } from "../../../chunk-2YXFLRQ6.js";
4
4
  import {
5
5
  express
6
- } from "../../../chunk-ZI67WIQS.js";
6
+ } from "../../../chunk-5IWHCXKN.js";
7
7
  import {
8
8
  h3
9
- } from "../../../chunk-HB4ZIH3T.js";
9
+ } from "../../../chunk-4RX4VQCB.js";
10
10
  import {
11
11
  fastify
12
- } from "../../../chunk-VOEWHQUB.js";
13
- import "../../../chunk-3GWM5GR3.js";
12
+ } from "../../../chunk-CRP6Y7NF.js";
13
+ import "../../../chunk-EPB3GQNL.js";
14
14
  import "../../../chunk-ETBFOLQN.js";
15
15
 
16
16
  // src/middleware/pubsub/server/index.ts