@runlane/realtime 0.0.0-stage → 0.5.1
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.
- package/LICENSE +202 -0
- package/dist/client.cjs +534 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +33 -0
- package/dist/client.d.cts.map +1 -0
- package/dist/client.d.mts +33 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +508 -0
- package/dist/client.mjs.map +1 -0
- package/dist/index.cjs +686 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +62 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +62 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +667 -0
- package/dist/index.mjs.map +1 -0
- package/dist/wire-BEevjo-K.mjs +53 -0
- package/dist/wire-BEevjo-K.mjs.map +1 -0
- package/dist/wire-xCr4QuVn.cjs +115 -0
- package/dist/wire-xCr4QuVn.cjs.map +1 -0
- package/package.json +83 -3
- package/README.md +0 -3
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ErrorCode, RealtimeConnectionChange, RunCursor, RunId, RunMeta, RunOutput, RunStreamName, RunStreamName as RunStreamName$1, RunStreamPart, RunStreamPart as RunStreamPart$1, RunStreamRecord, RunStreamRecord as RunStreamRecord$1, RunStreamRecordType, RunStreams, RunSubscribeOptions, RunSubscriptionEvent, RunSubscriptionEvent as RunSubscriptionEvent$1, RunSubscriptionEventType, RunTypes, RunlaneError, RunlaneIdInput, RunlaneTelemetryRecordingContext } from "@runlane/contracts/realtime";
|
|
2
|
+
import { Awaitable } from "@runlane/utilities/browser";
|
|
3
|
+
/** Run and cancellation context for an initial token request or a refresh after denial. */
|
|
4
|
+
export interface RunTokenRequest {
|
|
5
|
+
readonly runId: RunId;
|
|
6
|
+
readonly signal: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
/** Browser/Node Fetch client; the application owns its token refresh callback and lifetime. */
|
|
9
|
+
export interface RunlaneClientOptions extends RunlaneTelemetryRecordingContext {
|
|
10
|
+
readonly baseUrl: string;
|
|
11
|
+
/** Requests an initial token when missing, or refreshes once after HTTP access denial. */
|
|
12
|
+
readonly refreshAccessToken?: (request: RunTokenRequest) => Awaitable<string>;
|
|
13
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
14
|
+
}
|
|
15
|
+
/** Initial token, resume cursor, cancellation, and connection notifications shared by both readers. */
|
|
16
|
+
export type RunlaneClientReadOptions = Pick<RunSubscribeOptions, 'after' | 'signal'> & {
|
|
17
|
+
readonly accessToken?: string;
|
|
18
|
+
readonly onConnectionChange?: (change: RealtimeConnectionChange) => void;
|
|
19
|
+
};
|
|
20
|
+
/** Subscription controls; omit streams to read all streams permitted by the grant. */
|
|
21
|
+
export type RunlaneClientSubscribeOptions = RunlaneClientReadOptions & {
|
|
22
|
+
readonly streams?: false;
|
|
23
|
+
};
|
|
24
|
+
/** Pull-based readers validate input on the first next(), before effects. Task generics add types only; abort ends normally. */
|
|
25
|
+
export interface RunlaneClient {
|
|
26
|
+
subscribe<TTask = unknown>(runId: RunlaneIdInput, options?: RunlaneClientSubscribeOptions): AsyncGenerator<RunSubscriptionEvent$1<TTask>, void>;
|
|
27
|
+
/** Supply TName explicitly for an exact chunk type when TTask declares several streams. */
|
|
28
|
+
readStream<TTask = unknown, TName extends RunStreamName$1<TTask> = RunStreamName$1<TTask>>(runId: RunlaneIdInput, name: TName, options?: RunlaneClientReadOptions): AsyncGenerator<RunStreamRecord$1<RunStreamPart$1<TTask, TName>>, void>;
|
|
29
|
+
}
|
|
30
|
+
/** Creates a reusable authenticated streaming client with bounded parsing and reconnect delays. */
|
|
31
|
+
export declare function createRunlaneClient(options: RunlaneClientOptions): RunlaneClient;
|
|
32
|
+
export { ErrorCode, type RunCursor, type RunMeta, type RunOutput, type RunStreamName, type RunStreamPart, type RunStreamRecord, RunStreamRecordType, type RunStreams, type RunSubscriptionEvent, RunSubscriptionEventType, type RunTypes, RunlaneError };
|
|
33
|
+
//# sourceMappingURL=client.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.mts","names":["RunSubscriptionEvent","RunStreamName","RunStreamPart","RunStreamRecord"],"sources":["../src/client.ts"],"mappings":";;;iBA6CiB;WACN,OAAO;WACP,QAAQ;;;iBAIF,6BAA6B;WACnC;;WAEA,sBAAsB,SAAS,oBAAoB;WACnD,eAAe,WAAW;;;YAGzB,2BAA2B,KAAK;WACjC;WACA,sBAAsB,QAAQ;;;YAG7B,gCAAgC;WACjC;;;iBAGM;EACf,UAAU,iBACR,OAAO,gBACP,UAAU,gCACT,eAAeA,uBAAqB;;EAEvC,WAAW,iBAAiB,cAAcC,gBAAc,SAASA,gBAAc,QAC7E,OAAO,gBACP,MAAM,OACN,UAAU,2BACT,eAAeE,kBAAgBD,gBAAc,OAAO;;;wBAmDzC,oBAAoB,SAAS,uBAAuB"}
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import { a as invalidOptions, i as realtimeTiming, n as streamSelectionQuery, o as wireInvalid, r as realtimeClock, t as parseWireJson } from "./wire-BEevjo-K.mjs";
|
|
2
|
+
import { ErrorCode, ErrorCode as ErrorCode$1, RealtimeConnectionCloseReason, RealtimeConnectionState, RunReaderKind, RunStreamRecordType, RunSubscriptionEventType, RunSubscriptionEventType as RunSubscriptionEventType$1, RunlaneError, RunlaneError as RunlaneError$1, RunlaneMetricAttribute, RunlaneMetricName, createRunlaneIdSchema, realtimeDefaults, recordRunlaneTelemetryMetric, runCursorSchema, runRealtimeControlSchema, runRealtimeErrorSchema, runStatusValues, runStreamNameSchema, runStreamWireSchema, runSubscriptionWireSchema } from "@runlane/contracts/realtime";
|
|
3
|
+
import { createFunctionSchema, createUrlSchema, getDownwardJitteredDelayMs, objectSurfaceSchema, waitForAbortableTimeout } from "@runlane/utilities/browser";
|
|
4
|
+
import * as z$1 from "zod";
|
|
5
|
+
import { createParser } from "eventsource-parser";
|
|
6
|
+
// src/connection-observer.ts
|
|
7
|
+
/** Owns notification ordering and preserves the first callback failure without recursive reporting. */
|
|
8
|
+
var ConnectionObserver = class {
|
|
9
|
+
callback;
|
|
10
|
+
failed = false;
|
|
11
|
+
constructor(callback) {
|
|
12
|
+
this.callback = callback;
|
|
13
|
+
}
|
|
14
|
+
emit(change) {
|
|
15
|
+
try {
|
|
16
|
+
this.callback?.(change);
|
|
17
|
+
} catch (cause) {
|
|
18
|
+
this.failed = true;
|
|
19
|
+
throw cause instanceof RunlaneError$1 ? cause : new RunlaneError$1({
|
|
20
|
+
code: ErrorCode$1.InternalError,
|
|
21
|
+
message: "Realtime connection observer failed.",
|
|
22
|
+
cause
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
reconnect(signal, attempt, ceilingMs) {
|
|
27
|
+
if (signal.aborted) return void 0;
|
|
28
|
+
const delayMs = getDownwardJitteredDelayMs(ceilingMs);
|
|
29
|
+
this.emit({
|
|
30
|
+
state: RealtimeConnectionState.Reconnecting,
|
|
31
|
+
attempt,
|
|
32
|
+
delayMs
|
|
33
|
+
});
|
|
34
|
+
return delayMs;
|
|
35
|
+
}
|
|
36
|
+
close(signal, failure) {
|
|
37
|
+
if (this.failed) return;
|
|
38
|
+
if (failure !== void 0) {
|
|
39
|
+
this.emit({
|
|
40
|
+
state: RealtimeConnectionState.Closed,
|
|
41
|
+
reason: RealtimeConnectionCloseReason.Failed,
|
|
42
|
+
error: failure.cause
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const reason = signal.aborted ? RealtimeConnectionCloseReason.Aborted : RealtimeConnectionCloseReason.Completed;
|
|
47
|
+
this.emit({
|
|
48
|
+
state: RealtimeConnectionState.Closed,
|
|
49
|
+
reason
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
// src/sse.ts
|
|
54
|
+
const encoder = new TextEncoder();
|
|
55
|
+
/** Incremental UTF-8 SSE parser; incomplete trailing events are never dispatched. */
|
|
56
|
+
async function* readSse(body, signal) {
|
|
57
|
+
const reader = body.getReader();
|
|
58
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
59
|
+
let lastId = "";
|
|
60
|
+
const ready = {};
|
|
61
|
+
const budget = new FrameByteBudget();
|
|
62
|
+
const parser = createParser({
|
|
63
|
+
maxBufferSize: realtimeDefaults.maxEventBytes,
|
|
64
|
+
onId(id) {
|
|
65
|
+
lastId = id;
|
|
66
|
+
},
|
|
67
|
+
onEvent(message) {
|
|
68
|
+
ready.frame = {
|
|
69
|
+
event: message.event ?? "message",
|
|
70
|
+
id: lastId,
|
|
71
|
+
data: message.data
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
onError(error) {
|
|
75
|
+
if (error.type === "max-buffer-size-exceeded") throw wireInvalid(error);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
const abort = () => {
|
|
79
|
+
reader.cancel().catch(() => void 0);
|
|
80
|
+
};
|
|
81
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
82
|
+
if (signal.aborted) abort();
|
|
83
|
+
try {
|
|
84
|
+
while (!signal.aborted) {
|
|
85
|
+
const result = await readBytes(reader);
|
|
86
|
+
if (result.done) {
|
|
87
|
+
decodeUtf8(decoder);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const char of decodeUtf8(decoder, result.value)) {
|
|
91
|
+
budget.accept(char);
|
|
92
|
+
parser.feed(char);
|
|
93
|
+
if (ready.frame !== void 0) {
|
|
94
|
+
const frame = ready.frame;
|
|
95
|
+
delete ready.frame;
|
|
96
|
+
yield frame;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} finally {
|
|
101
|
+
signal.removeEventListener("abort", abort);
|
|
102
|
+
await reader.cancel().catch(() => void 0);
|
|
103
|
+
reader.releaseLock();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
var FrameByteBudget = class {
|
|
107
|
+
retained = 0;
|
|
108
|
+
lineEmpty = true;
|
|
109
|
+
skipLf = false;
|
|
110
|
+
resetAfterCr = false;
|
|
111
|
+
accept(char) {
|
|
112
|
+
if (this.resetAfterCr && char !== "\n") {
|
|
113
|
+
this.retained = 0;
|
|
114
|
+
this.resetAfterCr = false;
|
|
115
|
+
}
|
|
116
|
+
const point = char.codePointAt(0) ?? 0;
|
|
117
|
+
this.retained += point < 128 ? 1 : encoder.encode(char).byteLength;
|
|
118
|
+
if (this.retained > realtimeDefaults.maxEventBytes) throw wireInvalid();
|
|
119
|
+
if (this.skipLf && char === "\n") {
|
|
120
|
+
this.skipLf = false;
|
|
121
|
+
if (this.resetAfterCr) {
|
|
122
|
+
this.retained = 0;
|
|
123
|
+
this.resetAfterCr = false;
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
this.skipLf = char === "\r";
|
|
128
|
+
if (char !== "\r" && char !== "\n") {
|
|
129
|
+
this.lineEmpty = false;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (this.lineEmpty && char === "\n") this.retained = 0;
|
|
133
|
+
this.resetAfterCr = this.lineEmpty && char === "\r";
|
|
134
|
+
this.lineEmpty = true;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
async function readBytes(reader) {
|
|
138
|
+
try {
|
|
139
|
+
return await reader.read();
|
|
140
|
+
} catch (cause) {
|
|
141
|
+
throw new RunlaneError$1({
|
|
142
|
+
code: ErrorCode$1.Disconnected,
|
|
143
|
+
message: "Realtime connection was interrupted.",
|
|
144
|
+
retryable: true,
|
|
145
|
+
cause
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function decodeUtf8(decoder, bytes) {
|
|
150
|
+
try {
|
|
151
|
+
return bytes === void 0 ? decoder.decode() : decoder.decode(bytes, { stream: true });
|
|
152
|
+
} catch (cause) {
|
|
153
|
+
throw wireInvalid(cause);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// src/client.ts
|
|
157
|
+
const realtimeErrorEnvelopeSchema = z$1.object({ error: runRealtimeErrorSchema });
|
|
158
|
+
const clientOptionsSchema = z$1.strictObject({
|
|
159
|
+
baseUrl: createUrlSchema("baseUrl must be an absolute HTTP or HTTPS URL.").refine((base) => base.protocol === "https:" || base.protocol === "http:", "baseUrl must use HTTP or HTTPS.").refine((base) => !base.username && !base.password && !base.search && !base.hash, "baseUrl must not contain credentials, a query, or a fragment."),
|
|
160
|
+
refreshAccessToken: createFunctionSchema("refreshAccessToken must be a function.").optional(),
|
|
161
|
+
fetch: createFunctionSchema("fetch must be a function.").optional(),
|
|
162
|
+
telemetry: objectSurfaceSchema.optional(),
|
|
163
|
+
onTelemetryError: createFunctionSchema("onTelemetryError must be a function.").optional()
|
|
164
|
+
});
|
|
165
|
+
const readerFields = {
|
|
166
|
+
runId: createRunlaneIdSchema("Run id"),
|
|
167
|
+
after: runCursorSchema.optional()
|
|
168
|
+
};
|
|
169
|
+
const readerRequestSchema = z$1.discriminatedUnion("kind", [z$1.object({
|
|
170
|
+
...readerFields,
|
|
171
|
+
kind: z$1.literal(RunReaderKind.Subscription),
|
|
172
|
+
name: z$1.undefined(),
|
|
173
|
+
streams: z$1.literal(false).optional()
|
|
174
|
+
}), z$1.object({
|
|
175
|
+
...readerFields,
|
|
176
|
+
kind: z$1.literal(RunReaderKind.Stream),
|
|
177
|
+
name: runStreamNameSchema
|
|
178
|
+
})]);
|
|
179
|
+
/** Creates a reusable authenticated streaming client with bounded parsing and reconnect delays. */
|
|
180
|
+
function createRunlaneClient(options) {
|
|
181
|
+
const checked = clientOptionsSchema.safeParse(options);
|
|
182
|
+
if (!checked.success) throw invalidOptions(checked.error, "client");
|
|
183
|
+
const base = checked.data.baseUrl;
|
|
184
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
185
|
+
async function* subscribe(runId, input) {
|
|
186
|
+
for await (const event of connect(options, fetcher, base, runId, void 0, RunReaderKind.Subscription, input)) if (event.kind === RunReaderKind.Subscription) yield event.value;
|
|
187
|
+
}
|
|
188
|
+
async function* readStream(runId, name, input) {
|
|
189
|
+
for await (const event of connect(options, fetcher, base, runId, name, RunReaderKind.Stream, input)) if (event.kind === RunReaderKind.Stream) yield event.value;
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
subscribe,
|
|
193
|
+
readStream
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
async function* connect(options, fetcher, base, runId, name, kind, input = {}) {
|
|
197
|
+
const checked = readerRequestSchema.safeParse({
|
|
198
|
+
kind,
|
|
199
|
+
runId,
|
|
200
|
+
name,
|
|
201
|
+
after: input.after,
|
|
202
|
+
streams: input.streams
|
|
203
|
+
});
|
|
204
|
+
if (!checked.success) throw new RunlaneError$1({
|
|
205
|
+
code: checked.error.issues[0]?.path[0] === "after" ? ErrorCode$1.RunCursorInvalid : ErrorCode$1.ValidationFailed,
|
|
206
|
+
message: "Run id, stream name, stream selection, or after cursor is invalid.",
|
|
207
|
+
cause: checked.error
|
|
208
|
+
});
|
|
209
|
+
const request = checked.data;
|
|
210
|
+
const url = makeUrl(base, request.runId, request.name);
|
|
211
|
+
if (request.kind === RunReaderKind.Subscription && request.streams === false) url.searchParams.set(streamSelectionQuery.name, streamSelectionQuery.none);
|
|
212
|
+
const signal = input.signal ?? new AbortController().signal;
|
|
213
|
+
const observer = new ConnectionObserver(input.onConnectionChange);
|
|
214
|
+
let failure;
|
|
215
|
+
try {
|
|
216
|
+
yield* followConnections(options, fetcher, url, request.runId, kind, request.after, input.accessToken, signal, observer);
|
|
217
|
+
} catch (cause) {
|
|
218
|
+
const error = cause instanceof RunlaneError$1 ? cause : new RunlaneError$1({
|
|
219
|
+
code: ErrorCode$1.InternalError,
|
|
220
|
+
message: "Realtime reader failed.",
|
|
221
|
+
cause
|
|
222
|
+
});
|
|
223
|
+
failure = { cause: error };
|
|
224
|
+
throw error;
|
|
225
|
+
} finally {
|
|
226
|
+
observer.close(signal, failure);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function* followConnections(options, fetcher, url, runId, kind, after, accessToken, signal, observer) {
|
|
230
|
+
const retry = new ReconnectState(after, accessToken, options.refreshAccessToken);
|
|
231
|
+
const named = kind === RunReaderKind.Stream;
|
|
232
|
+
const operation = named ? "read" : "subscribe";
|
|
233
|
+
while (!signal.aborted) {
|
|
234
|
+
observer.emit({ state: RealtimeConnectionState.Connecting });
|
|
235
|
+
for await (const delivered of connectionEvents(fetcher, url, runId, retry, signal, named, observer)) {
|
|
236
|
+
if (!(delivered.kind === RunReaderKind.Stream && !retry.deliverRecord(delivered.value.id)) && delivered.value !== void 0) yield delivered;
|
|
237
|
+
retry.delivered(delivered.cursor);
|
|
238
|
+
if (delivered.terminal) return;
|
|
239
|
+
}
|
|
240
|
+
const scheduled = retry.schedule(signal, observer);
|
|
241
|
+
if (scheduled === void 0) return;
|
|
242
|
+
recordRunlaneTelemetryMetric(options, {
|
|
243
|
+
name: RunlaneMetricName.RealtimeReconnectCount,
|
|
244
|
+
value: 1,
|
|
245
|
+
attributes: { [RunlaneMetricAttribute.Operation]: operation }
|
|
246
|
+
});
|
|
247
|
+
await waitForAbortableTimeout(scheduled, signal);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
var ReconnectState = class {
|
|
251
|
+
accessToken;
|
|
252
|
+
refreshAccessToken;
|
|
253
|
+
cursor;
|
|
254
|
+
records = /* @__PURE__ */ new Set();
|
|
255
|
+
delay = realtimeTiming.clientReconnectDelay;
|
|
256
|
+
attempts = 0;
|
|
257
|
+
failures = 0;
|
|
258
|
+
openedAt;
|
|
259
|
+
lastFailure;
|
|
260
|
+
needsRefresh;
|
|
261
|
+
refreshedAfterDenial = false;
|
|
262
|
+
constructor(after, accessToken, refreshAccessToken) {
|
|
263
|
+
this.accessToken = accessToken;
|
|
264
|
+
this.refreshAccessToken = refreshAccessToken;
|
|
265
|
+
this.cursor = after;
|
|
266
|
+
this.needsRefresh = accessToken === void 0 && refreshAccessToken !== void 0;
|
|
267
|
+
}
|
|
268
|
+
async readyToken(runId, signal) {
|
|
269
|
+
if (!this.needsRefresh || this.refreshAccessToken === void 0) return true;
|
|
270
|
+
try {
|
|
271
|
+
signal.throwIfAborted();
|
|
272
|
+
const token = await awaitToken(this.refreshAccessToken({
|
|
273
|
+
runId,
|
|
274
|
+
signal
|
|
275
|
+
}), signal);
|
|
276
|
+
signal.throwIfAborted();
|
|
277
|
+
this.accessToken = token;
|
|
278
|
+
this.needsRefresh = false;
|
|
279
|
+
return true;
|
|
280
|
+
} catch (cause) {
|
|
281
|
+
if (signal.aborted) return false;
|
|
282
|
+
if (cause instanceof RunlaneError$1 && !cause.retryable) throw cause;
|
|
283
|
+
this.lastFailure = cause instanceof RunlaneError$1 ? cause : new RunlaneError$1({
|
|
284
|
+
code: ErrorCode$1.Disconnected,
|
|
285
|
+
message: "Realtime token refresh is temporarily unavailable.",
|
|
286
|
+
retryable: true,
|
|
287
|
+
cause
|
|
288
|
+
});
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
refreshAfterDenial(status, error) {
|
|
293
|
+
if (status !== 403 || error.code !== ErrorCode$1.AccessDenied) return false;
|
|
294
|
+
if (this.refreshAccessToken === void 0 || this.refreshedAfterDenial) return false;
|
|
295
|
+
this.needsRefresh = true;
|
|
296
|
+
this.refreshedAfterDenial = true;
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
open() {
|
|
300
|
+
this.refreshedAfterDenial = false;
|
|
301
|
+
this.attempts = 0;
|
|
302
|
+
this.delay = realtimeTiming.clientReconnectDelay;
|
|
303
|
+
this.openedAt = realtimeClock.now();
|
|
304
|
+
}
|
|
305
|
+
deliverRecord(id) {
|
|
306
|
+
if (this.records.has(id)) return false;
|
|
307
|
+
this.records.add(id);
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
delivered(cursor) {
|
|
311
|
+
if (cursor === void 0 || cursor === this.cursor) return;
|
|
312
|
+
this.cursor = cursor;
|
|
313
|
+
this.records.clear();
|
|
314
|
+
this.failures = 0;
|
|
315
|
+
this.lastFailure = void 0;
|
|
316
|
+
this.attempts = 0;
|
|
317
|
+
this.delay = realtimeTiming.clientReconnectDelay;
|
|
318
|
+
}
|
|
319
|
+
schedule(signal, observer) {
|
|
320
|
+
if (signal.aborted) return void 0;
|
|
321
|
+
if (this.openedAt !== void 0 && realtimeClock.now() - this.openedAt >= realtimeTiming.heartbeatInterval) {
|
|
322
|
+
this.failures = 0;
|
|
323
|
+
this.lastFailure = void 0;
|
|
324
|
+
}
|
|
325
|
+
this.openedAt = void 0;
|
|
326
|
+
if (++this.failures >= realtimeDefaults.clientMaxReconnectAttempts) throw new RunlaneError$1({
|
|
327
|
+
code: ErrorCode$1.Disconnected,
|
|
328
|
+
message: "Realtime connection repeatedly failed without progress.",
|
|
329
|
+
retryable: false,
|
|
330
|
+
cause: this.lastFailure,
|
|
331
|
+
...this.cursor === void 0 ? {} : { meta: { cursor: this.cursor } }
|
|
332
|
+
});
|
|
333
|
+
const scheduled = observer.reconnect(signal, ++this.attempts, this.delay);
|
|
334
|
+
this.delay = Math.min(realtimeTiming.clientMaxReconnectDelay, this.delay * 2);
|
|
335
|
+
return scheduled;
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
async function* connectionEvents(fetcher, url, runId, retry, signal, named, observer) {
|
|
339
|
+
if (!await retry.readyToken(runId, signal)) return;
|
|
340
|
+
yield* readConnection(fetcher, url, retry, signal, named, observer);
|
|
341
|
+
}
|
|
342
|
+
async function* readConnection(fetcher, url, retry, signal, named, observer) {
|
|
343
|
+
let body;
|
|
344
|
+
try {
|
|
345
|
+
body = await requestStream(fetcher, url, retry, signal);
|
|
346
|
+
if (body === void 0) return;
|
|
347
|
+
retry.open();
|
|
348
|
+
observer.emit({ state: RealtimeConnectionState.Open });
|
|
349
|
+
for await (const event of readSse(body, signal)) yield decodeEvent(event, named);
|
|
350
|
+
} catch (cause) {
|
|
351
|
+
if (observer.failed) throw cause;
|
|
352
|
+
if (signal.aborted) return;
|
|
353
|
+
if (cause instanceof RunlaneError$1 && cause.retryable) {
|
|
354
|
+
retry.lastFailure = cause;
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
throw cause;
|
|
358
|
+
} finally {
|
|
359
|
+
if (body !== void 0 && !body.locked) await body.cancel().catch(() => void 0);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
async function requestStream(fetcher, url, retry, signal) {
|
|
363
|
+
signal.throwIfAborted();
|
|
364
|
+
const headers = new Headers({ accept: "text/event-stream" });
|
|
365
|
+
if (retry.accessToken !== void 0) headers.set("authorization", `Bearer ${retry.accessToken}`);
|
|
366
|
+
if (retry.cursor !== void 0) headers.set("last-event-id", retry.cursor);
|
|
367
|
+
const response = await fetchResponse(fetcher, url, headers, signal, retry);
|
|
368
|
+
if (response === void 0) return void 0;
|
|
369
|
+
if (response.body === null || !/^text\/event-stream(?:\s*;|$)/i.test(response.headers.get("content-type") ?? "")) throw wireInvalid();
|
|
370
|
+
return response.body;
|
|
371
|
+
}
|
|
372
|
+
async function awaitToken(token, signal) {
|
|
373
|
+
const aborted = Promise.withResolvers();
|
|
374
|
+
const abort = () => aborted.reject(signal.reason);
|
|
375
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
376
|
+
if (signal.aborted) abort();
|
|
377
|
+
try {
|
|
378
|
+
return await Promise.race([token, aborted.promise]);
|
|
379
|
+
} finally {
|
|
380
|
+
signal.removeEventListener("abort", abort);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async function fetchResponse(fetcher, url, headers, signal, retry) {
|
|
384
|
+
let response;
|
|
385
|
+
try {
|
|
386
|
+
response = await fetcher(url, {
|
|
387
|
+
headers,
|
|
388
|
+
signal,
|
|
389
|
+
cache: "no-store",
|
|
390
|
+
redirect: "error"
|
|
391
|
+
});
|
|
392
|
+
} catch (cause) {
|
|
393
|
+
throw new RunlaneError$1({
|
|
394
|
+
code: ErrorCode$1.Disconnected,
|
|
395
|
+
message: "Realtime connection was interrupted.",
|
|
396
|
+
retryable: true,
|
|
397
|
+
cause
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
if (response.status >= 500 && response.headers.get("content-type")?.includes("application/json")) throw await responseError(response, signal);
|
|
401
|
+
if (response.status === 429 || response.status >= 500) {
|
|
402
|
+
await response.body?.cancel();
|
|
403
|
+
throw new RunlaneError$1({
|
|
404
|
+
code: ErrorCode$1.Disconnected,
|
|
405
|
+
message: "Realtime server is temporarily unavailable.",
|
|
406
|
+
retryable: true
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
if (!response.ok) {
|
|
410
|
+
const error = await responseError(response, signal);
|
|
411
|
+
if (retry.refreshAfterDenial(response.status, error)) return void 0;
|
|
412
|
+
throw error;
|
|
413
|
+
}
|
|
414
|
+
return response;
|
|
415
|
+
}
|
|
416
|
+
function decodeEvent(event, named) {
|
|
417
|
+
const raw = parseWireJson(event.data);
|
|
418
|
+
if (event.event === "error") {
|
|
419
|
+
const result = runRealtimeErrorSchema.safeParse(raw);
|
|
420
|
+
if (!result.success) throw wireInvalid(result.error);
|
|
421
|
+
const { cursor, ...error } = result.data;
|
|
422
|
+
throw new RunlaneError$1({
|
|
423
|
+
...error,
|
|
424
|
+
...cursor === void 0 ? {} : { meta: { cursor } }
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
if (event.event === "end" || event.event === "checkpoint") {
|
|
428
|
+
if (!runRealtimeControlSchema.safeParse(raw).success) throw wireInvalid();
|
|
429
|
+
if (event.event === "end") return {
|
|
430
|
+
kind: "control",
|
|
431
|
+
terminal: true
|
|
432
|
+
};
|
|
433
|
+
const cursor = runCursorSchema.safeParse(event.id);
|
|
434
|
+
if (!cursor.success) throw wireInvalid(cursor.error);
|
|
435
|
+
return {
|
|
436
|
+
kind: "control",
|
|
437
|
+
cursor: cursor.data
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
return named ? decodeNamedEvent(event, raw) : decodeSubscriptionEvent(event, raw);
|
|
441
|
+
}
|
|
442
|
+
function decodeNamedEvent(event, raw) {
|
|
443
|
+
const result = runStreamWireSchema.safeParse(raw);
|
|
444
|
+
if (!result.success || event.event !== "stream" || result.data.cursor !== void 0 && result.data.cursor !== event.id) throw wireInvalid();
|
|
445
|
+
return {
|
|
446
|
+
kind: RunReaderKind.Stream,
|
|
447
|
+
value: result.data.record,
|
|
448
|
+
cursor: result.data.cursor
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function decodeSubscriptionEvent(event, raw) {
|
|
452
|
+
const result = runSubscriptionWireSchema.safeParse(raw);
|
|
453
|
+
if (!result.success || String(result.data.type) !== event.event || result.data.cursor !== event.id) throw wireInvalid();
|
|
454
|
+
return {
|
|
455
|
+
kind: RunReaderKind.Subscription,
|
|
456
|
+
value: result.data,
|
|
457
|
+
cursor: result.data.cursor,
|
|
458
|
+
terminal: result.data.type === RunSubscriptionEventType$1.Status && runStatusValues.isTerminal(result.data.status)
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
async function responseError(response, signal) {
|
|
462
|
+
if (response.body === null) throw wireInvalid();
|
|
463
|
+
const reader = response.body.getReader();
|
|
464
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
465
|
+
let text = "";
|
|
466
|
+
let bytes = 0;
|
|
467
|
+
const abort = () => {
|
|
468
|
+
reader.cancel().catch(() => void 0);
|
|
469
|
+
};
|
|
470
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
471
|
+
try {
|
|
472
|
+
while (!signal.aborted) {
|
|
473
|
+
const chunk = await reader.read();
|
|
474
|
+
if (chunk.done) break;
|
|
475
|
+
bytes += chunk.value.byteLength;
|
|
476
|
+
if (bytes > realtimeDefaults.maxBufferedBytes) throw wireInvalid();
|
|
477
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
478
|
+
}
|
|
479
|
+
const raw = parseWireJson(text + decoder.decode());
|
|
480
|
+
const result = realtimeErrorEnvelopeSchema.safeParse(raw);
|
|
481
|
+
if (!result.success) throw wireInvalid(result.error);
|
|
482
|
+
const { cursor, ...error } = result.data.error;
|
|
483
|
+
return new RunlaneError$1({
|
|
484
|
+
...error,
|
|
485
|
+
...cursor === void 0 ? {} : { meta: { cursor } },
|
|
486
|
+
retryable: response.status >= 500 && result.data.error.retryable
|
|
487
|
+
});
|
|
488
|
+
} finally {
|
|
489
|
+
signal.removeEventListener("abort", abort);
|
|
490
|
+
await reader.cancel().catch(() => void 0);
|
|
491
|
+
reader.releaseLock();
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function makeUrl(base, runId, name) {
|
|
495
|
+
try {
|
|
496
|
+
const path = name === void 0 ? "events" : `streams/${encodeURIComponent(name)}`;
|
|
497
|
+
return new URL(`${base.href.replace(/\/$/, "")}/runs/${encodeURIComponent(runId)}/${path}`);
|
|
498
|
+
} catch (cause) {
|
|
499
|
+
throw new RunlaneError$1({
|
|
500
|
+
code: ErrorCode$1.ValidationFailed,
|
|
501
|
+
message: "Run id or stream name cannot be encoded in a URL.",
|
|
502
|
+
cause
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
export { ErrorCode, RunStreamRecordType, RunSubscriptionEventType, RunlaneError, createRunlaneClient };
|
|
507
|
+
|
|
508
|
+
//# sourceMappingURL=client.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.mjs","names":["RunlaneError","ErrorCode","RunlaneError","ErrorCode","z","RunlaneError","ErrorCode","RunSubscriptionEventType"],"sources":["../src/connection-observer.ts","../src/sse.ts","../src/client.ts"],"sourcesContent":["import {\n ErrorCode,\n RealtimeConnectionCloseReason,\n RealtimeConnectionState,\n RunlaneError,\n type RealtimeConnectionChange,\n} from '@runlane/contracts/realtime'\nimport { getDownwardJitteredDelayMs } from '@runlane/utilities/browser'\n\n/** Owns notification ordering and preserves the first callback failure without recursive reporting. */\nexport class ConnectionObserver {\n failed = false\n\n constructor(private readonly callback: ((change: RealtimeConnectionChange) => void) | undefined) {}\n\n emit(change: RealtimeConnectionChange): void {\n try {\n this.callback?.(change)\n } catch (cause) {\n // Once the observer throws, calling it again could replace the original failure.\n this.failed = true\n throw cause instanceof RunlaneError\n ? cause\n : new RunlaneError({\n code: ErrorCode.InternalError,\n message: 'Realtime connection observer failed.',\n cause,\n })\n }\n }\n\n reconnect(signal: AbortSignal, attempt: number, ceilingMs: number): number | undefined {\n if (signal.aborted) return undefined\n const delayMs = getDownwardJitteredDelayMs(ceilingMs)\n this.emit({ state: RealtimeConnectionState.Reconnecting, attempt, delayMs })\n return delayMs\n }\n\n close(signal: AbortSignal, failure: { readonly cause: RunlaneError } | undefined): void {\n if (this.failed) return\n if (failure !== undefined) {\n this.emit({\n state: RealtimeConnectionState.Closed,\n reason: RealtimeConnectionCloseReason.Failed,\n error: failure.cause,\n })\n return\n }\n const reason = signal.aborted ? RealtimeConnectionCloseReason.Aborted : RealtimeConnectionCloseReason.Completed\n this.emit({ state: RealtimeConnectionState.Closed, reason })\n }\n}\n","import { ErrorCode, realtimeDefaults, RunlaneError } from '@runlane/contracts/realtime'\nimport { createParser } from 'eventsource-parser'\n\nimport { wireInvalid } from './errors.js'\n\nconst encoder = new TextEncoder()\n\n/** One parsed SSE event; id retains the preceding checkpoint when omitted by the sender. */\nexport interface SseEvent {\n readonly event: string\n readonly id: string\n readonly data: string\n}\n\n/** Incremental UTF-8 SSE parser; incomplete trailing events are never dispatched. */\nexport async function* readSse(body: ReadableStream<Uint8Array>, signal: AbortSignal): AsyncGenerator<SseEvent, void> {\n const reader = body.getReader()\n const decoder = new TextDecoder('utf-8', { fatal: true })\n let lastId = ''\n const ready: { frame?: SseEvent } = {}\n const budget = new FrameByteBudget()\n const parser = createParser({\n maxBufferSize: realtimeDefaults.maxEventBytes,\n onId(id) {\n lastId = id\n },\n onEvent(message) {\n ready.frame = { event: message.event ?? 'message', id: lastId, data: message.data }\n },\n onError(error) {\n if (error.type === 'max-buffer-size-exceeded') throw wireInvalid(error)\n },\n })\n const abort = () => {\n void reader.cancel().catch(() => undefined)\n }\n signal.addEventListener('abort', abort, { once: true })\n if (signal.aborted) abort()\n try {\n while (!signal.aborted) {\n const result = await readBytes(reader)\n if (result.done) {\n decodeUtf8(decoder)\n return\n }\n for (const char of decodeUtf8(decoder, result.value)) {\n budget.accept(char)\n parser.feed(char)\n if (ready.frame !== undefined) {\n const frame = ready.frame\n delete ready.frame\n yield frame\n }\n }\n }\n } finally {\n signal.removeEventListener('abort', abort)\n // Cancellation may race a disconnected socket; it cannot change the already delivered result.\n await reader.cancel().catch(() => undefined)\n reader.releaseLock()\n }\n}\n\n// Parser buffers count selected characters; this guard counts every raw UTF-8 field and comment.\nclass FrameByteBudget {\n private retained = 0\n private lineEmpty = true\n private skipLf = false\n private resetAfterCr = false\n\n accept(char: string): void {\n if (this.resetAfterCr && char !== '\\n') {\n this.retained = 0\n this.resetAfterCr = false\n }\n const point = char.codePointAt(0) ?? 0\n this.retained += point < 128 ? 1 : encoder.encode(char).byteLength\n if (this.retained > realtimeDefaults.maxEventBytes) throw wireInvalid()\n if (this.skipLf && char === '\\n') {\n this.skipLf = false\n if (this.resetAfterCr) {\n this.retained = 0\n this.resetAfterCr = false\n }\n return\n }\n this.skipLf = char === '\\r'\n if (char !== '\\r' && char !== '\\n') {\n this.lineEmpty = false\n return\n }\n if (this.lineEmpty && char === '\\n') this.retained = 0\n this.resetAfterCr = this.lineEmpty && char === '\\r'\n this.lineEmpty = true\n }\n}\n\nasync function readBytes(reader: ReadableStreamDefaultReader<Uint8Array>) {\n try {\n return await reader.read()\n } catch (cause) {\n throw new RunlaneError({\n code: ErrorCode.Disconnected,\n message: 'Realtime connection was interrupted.',\n retryable: true,\n cause,\n })\n }\n}\n\nfunction decodeUtf8(decoder: TextDecoder, bytes?: Uint8Array): string {\n try {\n return bytes === undefined ? decoder.decode() : decoder.decode(bytes, { stream: true })\n } catch (cause) {\n throw wireInvalid(cause)\n }\n}\n","import {\n createRunlaneIdSchema,\n ErrorCode,\n RealtimeConnectionState,\n realtimeDefaults,\n recordRunlaneTelemetryMetric,\n runCursorSchema,\n RunlaneError,\n RunlaneMetricAttribute,\n RunlaneMetricName,\n RunReaderKind,\n runRealtimeControlSchema,\n runRealtimeErrorSchema,\n runStatusValues,\n runStreamNameSchema,\n runStreamWireSchema,\n RunSubscriptionEventType,\n runSubscriptionWireSchema,\n type RealtimeConnectionChange,\n type RunCursor,\n type RunId,\n type RunlaneIdInput,\n type RunlaneTelemetryRecordingContext,\n type RunStreamName,\n type RunStreamPart,\n type RunStreamRecord,\n type RunSubscribeOptions,\n type RunSubscriptionEvent,\n} from '@runlane/contracts/realtime'\nimport {\n createFunctionSchema,\n createUrlSchema,\n objectSurfaceSchema,\n waitForAbortableTimeout,\n type Awaitable,\n} from '@runlane/utilities/browser'\nimport * as z from 'zod'\n\nimport { ConnectionObserver } from './connection-observer.js'\nimport { invalidOptions, wireInvalid } from './errors.js'\nimport { readSse, type SseEvent } from './sse.js'\nimport { realtimeClock, realtimeTiming } from './time.js'\nimport { parseWireJson, streamSelectionQuery } from './wire.js'\n\n/** Run and cancellation context for an initial token request or a refresh after denial. */\nexport interface RunTokenRequest {\n readonly runId: RunId\n readonly signal: AbortSignal\n}\n\n/** Browser/Node Fetch client; the application owns its token refresh callback and lifetime. */\nexport interface RunlaneClientOptions extends RunlaneTelemetryRecordingContext {\n readonly baseUrl: string\n /** Requests an initial token when missing, or refreshes once after HTTP access denial. */\n readonly refreshAccessToken?: (request: RunTokenRequest) => Awaitable<string>\n readonly fetch?: typeof globalThis.fetch\n}\n/** Initial token, resume cursor, cancellation, and connection notifications shared by both readers. */\nexport type RunlaneClientReadOptions = Pick<RunSubscribeOptions, 'after' | 'signal'> & {\n readonly accessToken?: string\n readonly onConnectionChange?: (change: RealtimeConnectionChange) => void\n}\n/** Subscription controls; omit streams to read all streams permitted by the grant. */\nexport type RunlaneClientSubscribeOptions = RunlaneClientReadOptions & {\n readonly streams?: false\n}\n/** Pull-based readers validate input on the first next(), before effects. Task generics add types only; abort ends normally. */\nexport interface RunlaneClient {\n subscribe<TTask = unknown>(\n runId: RunlaneIdInput,\n options?: RunlaneClientSubscribeOptions,\n ): AsyncGenerator<RunSubscriptionEvent<TTask>, void>\n /** Supply TName explicitly for an exact chunk type when TTask declares several streams. */\n readStream<TTask = unknown, TName extends RunStreamName<TTask> = RunStreamName<TTask>>(\n runId: RunlaneIdInput,\n name: TName,\n options?: RunlaneClientReadOptions,\n ): AsyncGenerator<RunStreamRecord<RunStreamPart<TTask, TName>>, void>\n}\nconst realtimeErrorEnvelopeSchema = z.object({ error: runRealtimeErrorSchema })\n\ntype Delivered =\n | {\n readonly kind: RunReaderKind.Subscription\n readonly value: RunSubscriptionEvent\n readonly cursor: RunCursor\n readonly terminal?: boolean\n }\n | {\n readonly kind: RunReaderKind.Stream\n readonly value: RunStreamRecord\n readonly cursor?: RunCursor\n readonly terminal?: false\n }\n | { readonly kind: 'control'; readonly value?: never; readonly cursor?: RunCursor; readonly terminal?: boolean }\n\nconst clientOptionsSchema = z.strictObject({\n baseUrl: createUrlSchema('baseUrl must be an absolute HTTP or HTTPS URL.')\n .refine((base) => base.protocol === 'https:' || base.protocol === 'http:', 'baseUrl must use HTTP or HTTPS.')\n .refine(\n (base) => !base.username && !base.password && !base.search && !base.hash,\n 'baseUrl must not contain credentials, a query, or a fragment.',\n ),\n refreshAccessToken: createFunctionSchema<NonNullable<RunlaneClientOptions['refreshAccessToken']>>(\n 'refreshAccessToken must be a function.',\n ).optional(),\n fetch: createFunctionSchema<typeof fetch>('fetch must be a function.').optional(),\n telemetry: objectSurfaceSchema.optional(),\n onTelemetryError: createFunctionSchema<NonNullable<RunlaneClientOptions['onTelemetryError']>>(\n 'onTelemetryError must be a function.',\n ).optional(),\n})\n\nconst readerFields = {\n runId: createRunlaneIdSchema<'run'>('Run id'),\n after: runCursorSchema.optional(),\n}\nconst readerRequestSchema = z.discriminatedUnion('kind', [\n z.object({\n ...readerFields,\n kind: z.literal(RunReaderKind.Subscription),\n name: z.undefined(),\n streams: z.literal(false).optional(),\n }),\n z.object({ ...readerFields, kind: z.literal(RunReaderKind.Stream), name: runStreamNameSchema }),\n])\n\n/** Creates a reusable authenticated streaming client with bounded parsing and reconnect delays. */\nexport function createRunlaneClient(options: RunlaneClientOptions): RunlaneClient {\n const checked = clientOptionsSchema.safeParse(options)\n if (!checked.success) throw invalidOptions(checked.error, 'client')\n const base = checked.data.baseUrl\n const fetcher = options.fetch ?? globalThis.fetch\n function subscribe<TTask = unknown>(\n runId: RunlaneIdInput,\n input?: RunlaneClientSubscribeOptions,\n ): AsyncGenerator<RunSubscriptionEvent<TTask>, void>\n async function* subscribe(\n runId: RunlaneIdInput,\n input?: RunlaneClientSubscribeOptions,\n ): AsyncGenerator<RunSubscriptionEvent, void> {\n for await (const event of connect(options, fetcher, base, runId, undefined, RunReaderKind.Subscription, input)) {\n if (event.kind === RunReaderKind.Subscription) yield event.value\n }\n }\n function readStream<TTask = unknown, TName extends RunStreamName<TTask> = RunStreamName<TTask>>(\n runId: RunlaneIdInput,\n name: TName,\n input?: RunlaneClientReadOptions,\n ): AsyncGenerator<RunStreamRecord<RunStreamPart<TTask, TName>>, void>\n async function* readStream(\n runId: RunlaneIdInput,\n name: string,\n input?: RunlaneClientReadOptions,\n ): AsyncGenerator<RunStreamRecord, void> {\n for await (const event of connect(options, fetcher, base, runId, name, RunReaderKind.Stream, input)) {\n if (event.kind === RunReaderKind.Stream) yield event.value\n }\n }\n return { subscribe, readStream }\n}\n\nasync function* connect(\n options: RunlaneClientOptions,\n fetcher: typeof fetch,\n base: URL,\n runId: RunlaneIdInput,\n name: string | undefined,\n kind: RunReaderKind.Subscription | RunReaderKind.Stream,\n input: RunlaneClientSubscribeOptions = {},\n): AsyncGenerator<Delivered, void> {\n const checked = readerRequestSchema.safeParse({ kind, runId, name, after: input.after, streams: input.streams })\n if (!checked.success)\n throw new RunlaneError({\n code: checked.error.issues[0]?.path[0] === 'after' ? ErrorCode.RunCursorInvalid : ErrorCode.ValidationFailed,\n message: 'Run id, stream name, stream selection, or after cursor is invalid.',\n cause: checked.error,\n })\n const request = checked.data\n const url = makeUrl(base, request.runId, request.name)\n if (request.kind === RunReaderKind.Subscription && request.streams === false)\n url.searchParams.set(streamSelectionQuery.name, streamSelectionQuery.none)\n const signal = input.signal ?? new AbortController().signal\n const observer = new ConnectionObserver(input.onConnectionChange)\n let failure: { readonly cause: RunlaneError } | undefined\n try {\n yield* followConnections(\n options,\n fetcher,\n url,\n request.runId,\n kind,\n request.after,\n input.accessToken,\n signal,\n observer,\n )\n } catch (cause) {\n const error =\n cause instanceof RunlaneError\n ? cause\n : new RunlaneError({\n code: ErrorCode.InternalError,\n message: 'Realtime reader failed.',\n cause,\n })\n failure = { cause: error }\n throw error\n } finally {\n observer.close(signal, failure)\n }\n}\n\nasync function* followConnections(\n options: RunlaneClientOptions,\n fetcher: typeof fetch,\n url: URL,\n runId: RunId,\n kind: RunReaderKind.Subscription | RunReaderKind.Stream,\n after: RunCursor | undefined,\n accessToken: string | undefined,\n signal: AbortSignal,\n observer: ConnectionObserver,\n): AsyncGenerator<Delivered, void> {\n const retry = new ReconnectState(after, accessToken, options.refreshAccessToken)\n const named = kind === RunReaderKind.Stream\n const operation = named ? 'read' : 'subscribe'\n while (!signal.aborted) {\n observer.emit({ state: RealtimeConnectionState.Connecting })\n for await (const delivered of connectionEvents(fetcher, url, runId, retry, signal, named, observer)) {\n const duplicate = delivered.kind === RunReaderKind.Stream && !retry.deliverRecord(delivered.value.id)\n if (!duplicate && delivered.value !== undefined) yield delivered\n // Resume only after the caller has received the value.\n retry.delivered(delivered.cursor)\n if (delivered.terminal) return\n }\n const scheduled = retry.schedule(signal, observer)\n if (scheduled === undefined) return\n recordRunlaneTelemetryMetric(options, {\n name: RunlaneMetricName.RealtimeReconnectCount,\n value: 1,\n attributes: { [RunlaneMetricAttribute.Operation]: operation },\n })\n await waitForAbortableTimeout(scheduled, signal)\n }\n}\n\n// One owner for the current token, delivered checkpoint, and consecutive-failure budget.\nclass ReconnectState {\n cursor: RunCursor | undefined\n private readonly records = new Set<RunStreamRecord['id']>()\n private delay = realtimeTiming.clientReconnectDelay\n private attempts = 0\n private failures = 0\n private openedAt: number | undefined\n lastFailure: RunlaneError | undefined\n private needsRefresh: boolean\n private refreshedAfterDenial = false\n constructor(\n after: RunCursor | undefined,\n public accessToken: string | undefined,\n private readonly refreshAccessToken: RunlaneClientOptions['refreshAccessToken'],\n ) {\n this.cursor = after\n this.needsRefresh = accessToken === undefined && refreshAccessToken !== undefined\n }\n async readyToken(runId: RunId, signal: AbortSignal): Promise<boolean> {\n if (!this.needsRefresh || this.refreshAccessToken === undefined) return true\n try {\n signal.throwIfAborted()\n const token = await awaitToken(this.refreshAccessToken({ runId, signal }), signal)\n signal.throwIfAborted()\n this.accessToken = token\n this.needsRefresh = false\n return true\n } catch (cause) {\n if (signal.aborted) return false\n if (cause instanceof RunlaneError && !cause.retryable) throw cause\n this.lastFailure =\n cause instanceof RunlaneError\n ? cause\n : new RunlaneError({\n code: ErrorCode.Disconnected,\n message: 'Realtime token refresh is temporarily unavailable.',\n retryable: true,\n cause,\n })\n return false\n }\n }\n refreshAfterDenial(status: number, error: RunlaneError): boolean {\n if (status !== 403 || error.code !== ErrorCode.AccessDenied) return false\n if (this.refreshAccessToken === undefined || this.refreshedAfterDenial) return false\n this.needsRefresh = true\n this.refreshedAfterDenial = true\n return true\n }\n open(): void {\n this.refreshedAfterDenial = false\n this.attempts = 0\n this.delay = realtimeTiming.clientReconnectDelay\n this.openedAt = realtimeClock.now()\n }\n deliverRecord(id: RunStreamRecord['id']): boolean {\n if (this.records.has(id)) return false\n this.records.add(id)\n return true\n }\n delivered(cursor: RunCursor | undefined): void {\n if (cursor === undefined || cursor === this.cursor) return\n this.cursor = cursor\n this.records.clear()\n this.failures = 0\n this.lastFailure = undefined\n this.attempts = 0\n this.delay = realtimeTiming.clientReconnectDelay\n }\n schedule(signal: AbortSignal, observer: ConnectionObserver): number | undefined {\n if (signal.aborted) return undefined\n // A healthy idle connection can rotate for hours without producing a new run cursor.\n if (this.openedAt !== undefined && realtimeClock.now() - this.openedAt >= realtimeTiming.heartbeatInterval) {\n this.failures = 0\n this.lastFailure = undefined\n }\n this.openedAt = undefined\n if (++this.failures >= realtimeDefaults.clientMaxReconnectAttempts)\n throw new RunlaneError({\n code: ErrorCode.Disconnected,\n message: 'Realtime connection repeatedly failed without progress.',\n retryable: false,\n cause: this.lastFailure,\n ...(this.cursor === undefined ? {} : { meta: { cursor: this.cursor } }),\n })\n const scheduled = observer.reconnect(signal, ++this.attempts, this.delay)\n this.delay = Math.min(realtimeTiming.clientMaxReconnectDelay, this.delay * 2)\n return scheduled\n }\n}\n\nasync function* connectionEvents(\n fetcher: typeof fetch,\n url: URL,\n runId: RunId,\n retry: ReconnectState,\n signal: AbortSignal,\n named: boolean,\n observer: ConnectionObserver,\n): AsyncGenerator<Delivered, void> {\n // Permanent refresher errors bypass the retryable I/O catch.\n if (!(await retry.readyToken(runId, signal))) return\n yield* readConnection(fetcher, url, retry, signal, named, observer)\n}\n\nasync function* readConnection(\n fetcher: typeof fetch,\n url: URL,\n retry: ReconnectState,\n signal: AbortSignal,\n named: boolean,\n observer: ConnectionObserver,\n): AsyncGenerator<Delivered, void> {\n let body: ReadableStream<Uint8Array> | undefined\n try {\n body = await requestStream(fetcher, url, retry, signal)\n if (body === undefined) return\n retry.open()\n observer.emit({ state: RealtimeConnectionState.Open })\n for await (const event of readSse(body, signal)) yield decodeEvent(event, named)\n } catch (cause) {\n if (observer.failed) throw cause\n if (signal.aborted) return\n if (cause instanceof RunlaneError && cause.retryable) {\n retry.lastFailure = cause\n return\n }\n throw cause\n } finally {\n if (body !== undefined && !body.locked) await body.cancel().catch(() => undefined)\n }\n}\n\nasync function requestStream(\n fetcher: typeof fetch,\n url: URL,\n retry: ReconnectState,\n signal: AbortSignal,\n): Promise<ReadableStream<Uint8Array> | undefined> {\n signal.throwIfAborted()\n const headers = new Headers({ accept: 'text/event-stream' })\n if (retry.accessToken !== undefined) headers.set('authorization', `Bearer ${retry.accessToken}`)\n if (retry.cursor !== undefined) headers.set('last-event-id', retry.cursor)\n const response = await fetchResponse(fetcher, url, headers, signal, retry)\n if (response === undefined) return undefined\n if (response.body === null || !/^text\\/event-stream(?:\\s*;|$)/i.test(response.headers.get('content-type') ?? ''))\n throw wireInvalid()\n return response.body\n}\n\nasync function awaitToken(token: Awaitable<string>, signal: AbortSignal): Promise<string> {\n const aborted = Promise.withResolvers<never>()\n const abort = () => aborted.reject(signal.reason)\n signal.addEventListener('abort', abort, { once: true })\n if (signal.aborted) abort()\n try {\n // The race observes late token rejection even after abort wins.\n return await Promise.race([token, aborted.promise])\n } finally {\n signal.removeEventListener('abort', abort)\n }\n}\n\nasync function fetchResponse(\n fetcher: typeof fetch,\n url: URL,\n headers: Headers,\n signal: AbortSignal,\n retry: ReconnectState,\n): Promise<Response | undefined> {\n let response: Response\n try {\n response = await fetcher(url, { headers, signal, cache: 'no-store', redirect: 'error' })\n } catch (cause) {\n throw new RunlaneError({\n code: ErrorCode.Disconnected,\n message: 'Realtime connection was interrupted.',\n retryable: true,\n cause,\n })\n }\n if (response.status >= 500 && response.headers.get('content-type')?.includes('application/json'))\n throw await responseError(response, signal)\n if (response.status === 429 || response.status >= 500) {\n await response.body?.cancel()\n throw new RunlaneError({\n code: ErrorCode.Disconnected,\n message: 'Realtime server is temporarily unavailable.',\n retryable: true,\n })\n }\n if (!response.ok) {\n const error = await responseError(response, signal)\n if (retry.refreshAfterDenial(response.status, error)) return undefined\n throw error\n }\n return response\n}\n\nfunction decodeEvent(event: SseEvent, named: boolean): Delivered {\n const raw = parseWireJson(event.data)\n if (event.event === 'error') {\n const result = runRealtimeErrorSchema.safeParse(raw)\n if (!result.success) throw wireInvalid(result.error)\n const { cursor, ...error } = result.data\n throw new RunlaneError({ ...error, ...(cursor === undefined ? {} : { meta: { cursor } }) })\n }\n if (event.event === 'end' || event.event === 'checkpoint') {\n if (!runRealtimeControlSchema.safeParse(raw).success) throw wireInvalid()\n if (event.event === 'end') return { kind: 'control', terminal: true }\n const cursor = runCursorSchema.safeParse(event.id)\n if (!cursor.success) throw wireInvalid(cursor.error)\n return { kind: 'control', cursor: cursor.data }\n }\n return named ? decodeNamedEvent(event, raw) : decodeSubscriptionEvent(event, raw)\n}\n\nfunction decodeNamedEvent(event: SseEvent, raw: unknown): Delivered {\n const result = runStreamWireSchema.safeParse(raw)\n if (\n !result.success ||\n event.event !== 'stream' ||\n (result.data.cursor !== undefined && result.data.cursor !== event.id)\n )\n throw wireInvalid()\n return { kind: RunReaderKind.Stream, value: result.data.record, cursor: result.data.cursor }\n}\n\nfunction decodeSubscriptionEvent(event: SseEvent, raw: unknown): Delivered {\n const result = runSubscriptionWireSchema.safeParse(raw)\n if (!result.success || String(result.data.type) !== event.event || result.data.cursor !== event.id)\n throw wireInvalid()\n return {\n kind: RunReaderKind.Subscription,\n value: result.data,\n cursor: result.data.cursor,\n terminal: result.data.type === RunSubscriptionEventType.Status && runStatusValues.isTerminal(result.data.status),\n }\n}\n\nasync function responseError(response: Response, signal: AbortSignal): Promise<RunlaneError> {\n if (response.body === null) throw wireInvalid()\n const reader = response.body.getReader()\n const decoder = new TextDecoder('utf-8', { fatal: true })\n let text = ''\n let bytes = 0\n const abort = () => {\n void reader.cancel().catch(() => undefined)\n }\n signal.addEventListener('abort', abort, { once: true })\n try {\n while (!signal.aborted) {\n const chunk = await reader.read()\n if (chunk.done) break\n bytes += chunk.value.byteLength\n if (bytes > realtimeDefaults.maxBufferedBytes) throw wireInvalid()\n text += decoder.decode(chunk.value, { stream: true })\n }\n const raw = parseWireJson(text + decoder.decode())\n const result = realtimeErrorEnvelopeSchema.safeParse(raw)\n if (!result.success) throw wireInvalid(result.error)\n const { cursor, ...error } = result.data.error\n return new RunlaneError({\n ...error,\n ...(cursor === undefined ? {} : { meta: { cursor } }),\n retryable: response.status >= 500 && result.data.error.retryable,\n })\n } finally {\n signal.removeEventListener('abort', abort)\n await reader.cancel().catch(() => undefined)\n reader.releaseLock()\n }\n}\n\nfunction makeUrl(base: URL, runId: RunId, name?: string): URL {\n try {\n const path = name === undefined ? 'events' : `streams/${encodeURIComponent(name)}`\n return new URL(`${base.href.replace(/\\/$/, '')}/runs/${encodeURIComponent(runId)}/${path}`)\n } catch (cause) {\n throw new RunlaneError({\n code: ErrorCode.ValidationFailed,\n message: 'Run id or stream name cannot be encoded in a URL.',\n cause,\n })\n }\n}\n\nexport {\n ErrorCode,\n RunlaneError,\n RunSubscriptionEventType,\n RunStreamRecordType,\n type RunSubscriptionEvent,\n type RunStreamRecord,\n type RunCursor,\n} from '@runlane/contracts/realtime'\n\nexport type {\n RunTypes,\n RunOutput,\n RunMeta,\n RunStreams,\n RunStreamName,\n RunStreamPart,\n} from '@runlane/contracts/realtime'\n"],"mappings":"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"}
|