@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/dist/index.cjs ADDED
@@ -0,0 +1,686 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_wire = require("./wire-xCr4QuVn.cjs");
3
+ let _runlane_contracts_realtime = require("@runlane/contracts/realtime");
4
+ let _runlane_utilities_browser = require("@runlane/utilities/browser");
5
+ let eventsource_encoder = require("eventsource-encoder");
6
+ let zod = require("zod");
7
+ zod = require_wire.__toESM(zod, 1);
8
+ let jose = require("jose");
9
+ // src/access.ts
10
+ function intersectGrants(app, token) {
11
+ if (token === void 0) return app;
12
+ const permissions = app.permissions.filter((permission) => token.permissions.includes(permission));
13
+ let streams = app.streams ?? token.streams;
14
+ if (app.streams !== void 0 && token.streams !== void 0) streams = app.streams.filter((name) => token.streams?.includes(name));
15
+ return {
16
+ permissions,
17
+ ...streams === void 0 ? {} : { streams }
18
+ };
19
+ }
20
+ function permitsStream(grant, name) {
21
+ return grant.permissions.includes(_runlane_contracts_realtime.RunReadPermission.Streams) && (grant.streams === void 0 || grant.streams.includes(name));
22
+ }
23
+ function projectEvent(event, grant) {
24
+ if (event.type === _runlane_contracts_realtime.RunSubscriptionEventType.Stream) return permitsStream(grant, event.record.name) ? event : void 0;
25
+ if (event.type === _runlane_contracts_realtime.RunSubscriptionEventType.Meta) return grant.permissions.includes(_runlane_contracts_realtime.RunReadPermission.Meta) ? event : void 0;
26
+ if (!grant.permissions.includes(_runlane_contracts_realtime.RunReadPermission.Status)) return void 0;
27
+ if (event.type === _runlane_contracts_realtime.RunSubscriptionEventType.Status) {
28
+ const status = { ...event };
29
+ if (!grant.permissions.includes(_runlane_contracts_realtime.RunReadPermission.Output)) {
30
+ delete status.output;
31
+ delete status.failure;
32
+ }
33
+ if (status.failure !== void 0) status.failure = _runlane_contracts_realtime.runRealtimeSnapshotSchema.shape.failure.parse(status.failure);
34
+ return status;
35
+ }
36
+ return {
37
+ ...event,
38
+ run: projectSnapshot(event.run, grant)
39
+ };
40
+ }
41
+ function projectSnapshot(source, grant) {
42
+ const { meta, output, failure, ...status } = _runlane_contracts_realtime.runRealtimeSnapshotSchema.parse(source);
43
+ return {
44
+ ...status,
45
+ ...grant.permissions.includes(_runlane_contracts_realtime.RunReadPermission.Meta) && meta !== void 0 ? { meta } : {},
46
+ ...grant.permissions.includes(_runlane_contracts_realtime.RunReadPermission.Output) ? {
47
+ ...output === void 0 ? {} : { output },
48
+ ...failure === void 0 ? {} : { failure }
49
+ } : {}
50
+ };
51
+ }
52
+ // src/tokens.ts
53
+ const tokenPartsSchema = zod.z.tuple([
54
+ zod.z.string(),
55
+ zod.z.string(),
56
+ zod.z.string()
57
+ ]);
58
+ const headerSchema = zod.z.strictObject({
59
+ alg: zod.z.literal("HS256"),
60
+ typ: zod.z.literal("JWT"),
61
+ kid: zod.z.string().min(1).max(128)
62
+ });
63
+ const algorithmSchema = zod.z.object({
64
+ name: zod.z.literal("HMAC"),
65
+ hash: zod.z.object({ name: zod.z.literal("SHA-256") }),
66
+ length: zod.z.number().int().min(256)
67
+ });
68
+ const encoder$1 = new TextEncoder();
69
+ const decoder = new TextDecoder("utf-8", { fatal: true });
70
+ const maximumTokenLength = 16384;
71
+ /** Strict construction schema; claim and header constraints remain the canonical owners. */
72
+ const realtimeTokenOptionsSchema = zod.z.strictObject({
73
+ audience: _runlane_contracts_realtime.verifiedRunTokenSchema.shape.audience,
74
+ keyId: headerSchema.shape.kid,
75
+ key: zod.z.instanceof(CryptoKey),
76
+ verifyKeys: zod.z.record(headerSchema.shape.kid, zod.z.instanceof(CryptoKey)).readonly().optional()
77
+ }).refine((tokens) => !Object.hasOwn(tokens.verifyKeys ?? {}, tokens.keyId), "tokens.keyId must not also appear in verifyKeys.");
78
+ const createTokenOptionsSchema = zod.z.strictObject({
79
+ runId: zod.z.string(),
80
+ grant: _runlane_contracts_realtime.runAccessGrantSchema.optional(),
81
+ expiresIn: (0, _runlane_contracts_realtime.createDurationSchema)("expiresIn must be whole seconds between 1s and realtime.maxTokenTTL.").optional()
82
+ });
83
+ /** Signs one run/environment/audience grant using an HMAC-SHA256 key of at least 256 bits. */
84
+ async function signRunToken(environment, tokens, input) {
85
+ try {
86
+ const options = createTokenOptionsSchema.parse(input);
87
+ const header = headerSchema.parse({
88
+ alg: "HS256",
89
+ typ: "JWT",
90
+ kid: tokens.keyId
91
+ });
92
+ requireHmacKey(tokens.key, "sign");
93
+ const ttl = tokenLifetime(options.expiresIn?.milliseconds);
94
+ const issuedAt = Math.floor(require_wire.realtimeClock.now() / 1e3);
95
+ const claims = _runlane_contracts_realtime.verifiedRunTokenSchema.parse({
96
+ environment,
97
+ runId: options.runId,
98
+ grant: options.grant ?? { permissions: Object.values(_runlane_contracts_realtime.RunReadPermission) },
99
+ audience: tokens.audience,
100
+ issuedAt,
101
+ expiresAt: issuedAt + ttl / 1e3,
102
+ version: 1
103
+ });
104
+ const token = await new jose.CompactSign(encoder$1.encode(JSON.stringify(claims))).setProtectedHeader(header).sign(tokens.key);
105
+ if (token.length > maximumTokenLength) throw invalidToken();
106
+ return token;
107
+ } catch (cause) {
108
+ if (cause instanceof _runlane_contracts_realtime.RunlaneError) throw cause;
109
+ const message = cause instanceof zod.z.ZodError ? `Token ${cause.issues[0]?.path.join(".") ?? "expiresIn"} is invalid: ${cause.issues[0]?.message ?? "Check the token options."}` : "Token signing failed; check key and claims.";
110
+ const field = cause instanceof zod.z.ZodError ? cause.issues[0]?.path[0] : void 0;
111
+ let code = _runlane_contracts_realtime.ErrorCode.InternalError;
112
+ if (cause instanceof zod.z.ZodError) code = field === "environment" || field === "runId" || field === "grant" ? _runlane_contracts_realtime.ErrorCode.ValidationFailed : _runlane_contracts_realtime.ErrorCode.ConfigurationInvalid;
113
+ throw new _runlane_contracts_realtime.RunlaneError({
114
+ code,
115
+ message,
116
+ retryable: false,
117
+ cause
118
+ });
119
+ }
120
+ }
121
+ function tokenLifetime(ttl = require_wire.realtimeTiming.tokenTTL) {
122
+ if (ttl < 1e3 || ttl > require_wire.realtimeTiming.maxTokenTTL || ttl % 1e3 !== 0) throw new _runlane_contracts_realtime.RunlaneError({
123
+ code: _runlane_contracts_realtime.ErrorCode.ConfigurationInvalid,
124
+ message: "expiresIn must be whole seconds between 1s and realtime.maxTokenTTL.",
125
+ retryable: false
126
+ });
127
+ return ttl;
128
+ }
129
+ /** Verifies a scoped token and records only bounded rejection reasons through the supplied telemetry. */
130
+ async function verifyRunToken(token, options, diagnostics = {}, operation = "verify") {
131
+ let reason = "malformed";
132
+ try {
133
+ if (token.length > maximumTokenLength) throw invalidToken();
134
+ const [head, body, signature] = tokenPartsSchema.parse(token.split("."));
135
+ reason = "header";
136
+ const header = headerSchema.parse(JSON.parse(decoder.decode(decodeBase64(head))));
137
+ reason = "unknown_key";
138
+ const key = Object.hasOwn(options.keys, header.kid) ? options.keys[header.kid] : void 0;
139
+ if (key === void 0) throw invalidToken();
140
+ reason = "key";
141
+ requireHmacKey(key, "verify");
142
+ reason = "signature";
143
+ decodeBase64(signature);
144
+ const verified = await (0, jose.compactVerify)(token, key, { algorithms: ["HS256"] });
145
+ reason = "claims";
146
+ decodeBase64(body);
147
+ const claims = _runlane_contracts_realtime.verifiedRunTokenSchema.parse(JSON.parse(decoder.decode(verified.payload)));
148
+ const now = Math.floor(require_wire.realtimeClock.now() / 1e3);
149
+ const rejection = tokenClaimRejection(claims, options.audience, now);
150
+ if (rejection !== void 0) {
151
+ reason = rejection;
152
+ throw invalidToken();
153
+ }
154
+ return claims;
155
+ } catch (cause) {
156
+ (0, _runlane_contracts_realtime.recordRunlaneTelemetryMetric)(diagnostics, {
157
+ name: _runlane_contracts_realtime.RunlaneMetricName.RealtimeTokenRejectedCount,
158
+ value: 1,
159
+ attributes: {
160
+ [_runlane_contracts_realtime.RunlaneMetricAttribute.Operation]: operation,
161
+ [_runlane_contracts_realtime.RunlaneMetricAttribute.Reason]: reason
162
+ }
163
+ });
164
+ (0, _runlane_contracts_realtime.recordRunlaneTelemetryLog)(diagnostics, {
165
+ type: _runlane_contracts_realtime.RunlaneLogEventType.RealtimeTokenRejected,
166
+ level: _runlane_contracts_realtime.RunlaneLogLevel.Warn,
167
+ attributes: {
168
+ [_runlane_contracts_realtime.RunlaneLogAttribute.Operation]: operation,
169
+ [_runlane_contracts_realtime.RunlaneLogAttribute.Reason]: reason
170
+ }
171
+ });
172
+ throw invalidToken(cause);
173
+ }
174
+ }
175
+ function tokenClaimRejection(claims, audience, now) {
176
+ if (claims.audience !== audience) return "audience";
177
+ if (claims.issuedAt > now + require_wire.realtimeTiming.issuedAtLeeway / 1e3) return "issued_at";
178
+ if (claims.expiresAt <= now) return "expired";
179
+ if (claims.expiresAt <= claims.issuedAt || (claims.expiresAt - claims.issuedAt) * 1e3 > require_wire.realtimeTiming.maxTokenTTL) return "lifetime";
180
+ }
181
+ /** Shared key policy for signing, verification, and handler startup checks. */
182
+ function requireHmacKey(key, usage) {
183
+ if (!(key instanceof CryptoKey) || key.type !== "secret" || !algorithmSchema.safeParse(key.algorithm).success || !key.usages.includes(usage)) throw new _runlane_contracts_realtime.RunlaneError({
184
+ code: _runlane_contracts_realtime.ErrorCode.ConfigurationInvalid,
185
+ message: `key must be an HMAC-SHA256 key of at least 256 bits with ${usage} usage.`,
186
+ retryable: false
187
+ });
188
+ }
189
+ function decodeBase64(value) {
190
+ if (!/^[A-Za-z0-9_-]+$/.test(value)) throw invalidToken();
191
+ const bytes = jose.base64url.decode(value);
192
+ if (jose.base64url.encode(bytes) !== value) throw invalidToken();
193
+ return bytes;
194
+ }
195
+ function invalidToken(cause) {
196
+ return new _runlane_contracts_realtime.RunlaneError({
197
+ code: _runlane_contracts_realtime.ErrorCode.AccessDenied,
198
+ message: "Run access denied.",
199
+ retryable: false,
200
+ cause
201
+ });
202
+ }
203
+ // src/handler.ts
204
+ const encoder = new TextEncoder();
205
+ const runIdSchema = (0, _runlane_contracts_realtime.createRunlaneIdSchema)("Run id");
206
+ const pollInterval = (0, _runlane_contracts_realtime.createDurationSchema)().parse(_runlane_contracts_realtime.contractDefaults.streams.pollInterval).milliseconds;
207
+ const handlerOptionsSchema = zod.strictObject({
208
+ runlane: _runlane_utilities_browser.objectSurfaceSchema,
209
+ authorize: (0, _runlane_utilities_browser.createFunctionSchema)("authorize must be a function.").optional(),
210
+ tokens: realtimeTokenOptionsSchema.optional(),
211
+ telemetry: _runlane_utilities_browser.objectSurfaceSchema.optional(),
212
+ onTelemetryError: (0, _runlane_utilities_browser.createFunctionSchema)("onTelemetryError must be a function.").optional()
213
+ }).refine((options) => options.authorize !== void 0 || options.tokens !== void 0, "Configure authorize for session access, tokens for bearer access, or both.");
214
+ /** Mounts authorized, bounded SSE reads without starting workers or adding runtime storage work. */
215
+ function createRunlaneRealtime(options) {
216
+ const checked = handlerOptionsSchema.safeParse(options);
217
+ if (!checked.success) throw require_wire.invalidOptions(checked.error, "handler");
218
+ const tokens = checked.data.tokens;
219
+ if (tokens !== void 0) {
220
+ requireHmacKey(tokens.key, "sign");
221
+ requireHmacKey(tokens.key, "verify");
222
+ for (const key of Object.values(tokens.verifyKeys ?? {})) requireHmacKey(key, "verify");
223
+ }
224
+ const verification = tokens === void 0 ? void 0 : {
225
+ audience: tokens.audience,
226
+ keys: {
227
+ ...tokens.verifyKeys,
228
+ [tokens.keyId]: tokens.key
229
+ }
230
+ };
231
+ return {
232
+ async createToken(input) {
233
+ if (tokens === void 0) throw new _runlane_contracts_realtime.RunlaneError({
234
+ code: _runlane_contracts_realtime.ErrorCode.ConfigurationInvalid,
235
+ message: "Configure tokens before calling createToken."
236
+ });
237
+ return signRunToken(options.runlane.environment, tokens, input);
238
+ },
239
+ async handle(request) {
240
+ let operation = "subscribe";
241
+ try {
242
+ const route = parseRoute(request);
243
+ operation = route.name === void 0 ? "subscribe" : "read";
244
+ const token = await requestToken(request, route.runId, options, verification);
245
+ return await openConnection(options, request, route, await authorizeGrant(options, request, route, token, operation), token);
246
+ } catch (cause) {
247
+ const error = publicError(cause);
248
+ if (error.code === _runlane_contracts_realtime.ErrorCode.AccessDenied) recordDenial(options, cause, operation);
249
+ return Response.json({ error }, {
250
+ status: request.method === "GET" ? httpStatus(error.code) : 405,
251
+ headers: {
252
+ "cache-control": "no-store",
253
+ ...request.method === "GET" ? {} : { allow: "GET" }
254
+ }
255
+ });
256
+ }
257
+ }
258
+ };
259
+ }
260
+ async function authorizeGrant(options, request, route, token, operation) {
261
+ let app;
262
+ try {
263
+ app = options.authorize === void 0 ? token?.grant : await options.authorize({
264
+ request,
265
+ runId: route.runId,
266
+ ...token === void 0 ? {} : { token }
267
+ });
268
+ } catch (cause) {
269
+ (0, _runlane_contracts_realtime.recordRunlaneTelemetryMetric)(options, {
270
+ name: _runlane_contracts_realtime.RunlaneMetricName.RealtimeAuthorizationErrorCount,
271
+ value: 1,
272
+ attributes: {
273
+ [_runlane_contracts_realtime.RunlaneMetricAttribute.Operation]: operation,
274
+ [_runlane_contracts_realtime.RunlaneMetricAttribute.Reason]: "authorize_exception"
275
+ }
276
+ });
277
+ (0, _runlane_contracts_realtime.recordRunlaneTelemetryLog)(options, {
278
+ type: _runlane_contracts_realtime.RunlaneLogEventType.RealtimeAuthorizationFailed,
279
+ level: _runlane_contracts_realtime.RunlaneLogLevel.Error,
280
+ attributes: {
281
+ [_runlane_contracts_realtime.RunlaneLogAttribute.Operation]: operation,
282
+ [_runlane_contracts_realtime.RunlaneLogAttribute.Reason]: "authorize_exception"
283
+ }
284
+ });
285
+ throw new _runlane_contracts_realtime.RunlaneError({
286
+ code: _runlane_contracts_realtime.ErrorCode.InternalError,
287
+ message: "Realtime authorization failed.",
288
+ cause
289
+ });
290
+ }
291
+ if (app === void 0) throw accessDenied();
292
+ const parsed = _runlane_contracts_realtime.runAccessGrantSchema.safeParse(app);
293
+ if (!parsed.success) throw invalidRequest("Realtime grant is invalid.", parsed.error);
294
+ const grant = intersectGrants(parsed.data, token?.grant);
295
+ if (grant.permissions.length === 0 || route.name !== void 0 && !permitsStream(grant, route.name)) throw accessDenied();
296
+ return grant;
297
+ }
298
+ function recordDenial(options, cause, operation) {
299
+ const reason = accessDenialReasonSchema.safeParse(cause instanceof _runlane_contracts_realtime.RunlaneError ? cause.meta?.reason : void 0).data ?? "token_rejected";
300
+ if (reason !== "token_rejected") (0, _runlane_contracts_realtime.recordRunlaneTelemetryLog)(options, {
301
+ type: _runlane_contracts_realtime.RunlaneLogEventType.RealtimeAuthorizationDenied,
302
+ level: _runlane_contracts_realtime.RunlaneLogLevel.Warn,
303
+ attributes: {
304
+ [_runlane_contracts_realtime.RunlaneLogAttribute.Operation]: operation,
305
+ [_runlane_contracts_realtime.RunlaneLogAttribute.Reason]: reason
306
+ }
307
+ });
308
+ (0, _runlane_contracts_realtime.recordRunlaneTelemetryMetric)(options, {
309
+ name: _runlane_contracts_realtime.RunlaneMetricName.RealtimeAuthorizationDeniedCount,
310
+ value: 1,
311
+ attributes: {
312
+ [_runlane_contracts_realtime.RunlaneMetricAttribute.Operation]: operation,
313
+ [_runlane_contracts_realtime.RunlaneMetricAttribute.Reason]: reason
314
+ }
315
+ });
316
+ }
317
+ function parseRoute(request) {
318
+ if (request.method !== "GET") throw new _runlane_contracts_realtime.RunlaneError({
319
+ code: _runlane_contracts_realtime.ErrorCode.ValidationFailed,
320
+ message: "Realtime routes require GET.",
321
+ retryable: false
322
+ });
323
+ const url = new URL(request.url);
324
+ if (Array.from(url.searchParams.keys()).some((key) => key !== "after" && key !== require_wire.streamSelectionQuery.name) || url.searchParams.getAll("after").length > 1) throw invalidRequest("Only one after and one streams query parameter are accepted; use Authorization for credentials.");
325
+ const match = /\/runs\/([^/]+)\/(events|streams\/([^/]+))$/.exec(url.pathname);
326
+ if (match?.[1] === void 0) throw new _runlane_contracts_realtime.RunlaneError({
327
+ code: _runlane_contracts_realtime.ErrorCode.RunNotFound,
328
+ message: "Realtime route was not found.",
329
+ retryable: false
330
+ });
331
+ let runId;
332
+ let name;
333
+ try {
334
+ runId = runIdSchema.parse(decodeURIComponent(match[1]));
335
+ name = match[3] === void 0 ? void 0 : _runlane_contracts_realtime.runStreamNameSchema.parse(decodeURIComponent(match[3]));
336
+ } catch (cause) {
337
+ throw invalidRequest("Run id or stream name is invalid.", cause);
338
+ }
339
+ const selections = url.searchParams.getAll(require_wire.streamSelectionQuery.name);
340
+ if (selections.length > 1 || selections.length === 1 && (name !== void 0 || selections[0] !== require_wire.streamSelectionQuery.none)) throw invalidRequest("Stream selection requires one streams=none parameter on the events route.");
341
+ return {
342
+ runId,
343
+ ...name === void 0 ? {} : { name },
344
+ ...selections.length === 1 ? { streams: false } : {},
345
+ ...parseResumeCursor(request, url)
346
+ };
347
+ }
348
+ function parseResumeCursor(request, url) {
349
+ const header = request.headers.get("last-event-id");
350
+ const query = url.searchParams.get("after");
351
+ if (header !== null && query !== null && header !== query) throw invalidCursor();
352
+ const raw = header ?? query;
353
+ const parsed = raw === null ? void 0 : _runlane_contracts_realtime.runCursorSchema.safeParse(raw);
354
+ if (parsed !== void 0 && !parsed.success) throw invalidCursor();
355
+ return parsed?.success ? { after: parsed.data } : {};
356
+ }
357
+ async function openConnection(options, request, route, grant, token) {
358
+ const started = require_wire.realtimeClock.now();
359
+ const operation = route.name === void 0 ? "subscribe" : "read";
360
+ const control = new AbortController();
361
+ const expires = Math.min(started + require_wire.realtimeTiming.connectionDuration, token === void 0 ? Infinity : token.expiresAt * 1e3);
362
+ const stop = () => control.abort();
363
+ request.signal.addEventListener("abort", stop, { once: true });
364
+ if (request.signal.aborted) stop();
365
+ const expiry = setTimeout(stop, Math.max(0, expires - require_wire.realtimeClock.now()));
366
+ const frames = sourceFrames(options.runlane, route, grant, control.signal);
367
+ let pending;
368
+ let cursor = route.after;
369
+ let closed = false;
370
+ const finish = () => {
371
+ if (closed) return;
372
+ closed = true;
373
+ clearTimeout(expiry);
374
+ request.signal.removeEventListener("abort", stop);
375
+ control.abort();
376
+ frames.return().catch(() => void 0);
377
+ (0, _runlane_contracts_realtime.recordRunlaneTelemetryMetric)(options, {
378
+ name: _runlane_contracts_realtime.RunlaneMetricName.RealtimeConnectionDurationMs,
379
+ value: Math.max(0, require_wire.realtimeClock.now() - started),
380
+ attributes: { [_runlane_contracts_realtime.RunlaneMetricAttribute.Operation]: operation }
381
+ });
382
+ };
383
+ const next = async () => {
384
+ if (control.signal.aborted || require_wire.realtimeClock.now() >= expires) return void 0;
385
+ pending ??= (0, _runlane_contracts_realtime.runWithRunlaneTelemetrySpan)(options, { name: route.name === void 0 ? _runlane_contracts_realtime.RunlaneSpanName.RealtimeSubscribe : _runlane_contracts_realtime.RunlaneSpanName.RealtimeRead }, () => frames.next()).catch((cause) => {
386
+ const { cursor, ...error } = publicSourceError(cause);
387
+ throw new _runlane_contracts_realtime.RunlaneError({
388
+ ...error,
389
+ cause,
390
+ ...cursor === void 0 ? {} : { meta: { cursor } }
391
+ });
392
+ });
393
+ const timer = new AbortController();
394
+ const abortTimer = () => timer.abort();
395
+ control.signal.addEventListener("abort", abortTimer, { once: true });
396
+ try {
397
+ const result = await Promise.race([pending, (0, _runlane_utilities_browser.waitForAbortableTimeout)(require_wire.realtimeTiming.heartbeatInterval, timer.signal).then(() => void 0)]);
398
+ if (require_wire.realtimeClock.now() >= expires) return void 0;
399
+ if (result === void 0) return encoder.encode((0, eventsource_encoder.encode)({ comment: "heartbeat" }));
400
+ pending = void 0;
401
+ if (result.done) return void 0;
402
+ const frame = result.value;
403
+ const bytes = encodeFrame(frame, cursor);
404
+ cursor = frame.cursor ?? cursor;
405
+ return bytes;
406
+ } finally {
407
+ timer.abort();
408
+ control.signal.removeEventListener("abort", abortTimer);
409
+ }
410
+ };
411
+ let first;
412
+ try {
413
+ first = await next();
414
+ } catch (cause) {
415
+ finish();
416
+ throw cause;
417
+ }
418
+ const body = new ReadableStream({
419
+ start(controller) {
420
+ const end = () => {
421
+ if (closed) return;
422
+ controller.close();
423
+ finish();
424
+ };
425
+ control.signal.addEventListener("abort", end, { once: true });
426
+ if (control.signal.aborted) end();
427
+ },
428
+ async pull(controller) {
429
+ try {
430
+ const bytes = control.signal.aborted || require_wire.realtimeClock.now() >= expires ? void 0 : first ?? await next();
431
+ if (closed) return;
432
+ first = void 0;
433
+ if (bytes === void 0) {
434
+ controller.close();
435
+ finish();
436
+ return;
437
+ }
438
+ controller.enqueue(bytes);
439
+ } catch (cause) {
440
+ if (closed) return;
441
+ controller.enqueue(encoder.encode((0, eventsource_encoder.encode)({
442
+ event: "error",
443
+ data: JSON.stringify(publicError(cause, cursor))
444
+ })));
445
+ controller.close();
446
+ finish();
447
+ }
448
+ },
449
+ cancel() {
450
+ finish();
451
+ }
452
+ }, { highWaterMark: 0 });
453
+ return new Response(body, { headers: {
454
+ "content-type": "text/event-stream; charset=utf-8",
455
+ "cache-control": "no-store",
456
+ "x-accel-buffering": "no"
457
+ } });
458
+ }
459
+ function encodeFrame(frame, cursor) {
460
+ const data = JSON.stringify(frame.data);
461
+ const bytes = encoder.encode((0, eventsource_encoder.encode)({
462
+ event: frame.event,
463
+ data,
464
+ ...frame.cursor === void 0 ? {} : { id: frame.cursor }
465
+ }));
466
+ if (bytes.byteLength > _runlane_contracts_realtime.realtimeDefaults.maxEventBytes) throw new _runlane_contracts_realtime.RunlaneError({
467
+ code: _runlane_contracts_realtime.ErrorCode.EventTooLarge,
468
+ message: "Realtime event exceeds the serialized UTF-8 byte limit.",
469
+ retryable: false,
470
+ ...cursor === void 0 ? {} : { meta: { cursor } }
471
+ });
472
+ return bytes;
473
+ }
474
+ async function* sourceFrames(source, route, grant, signal) {
475
+ if (route.name === void 0) {
476
+ for await (const event of source.runs.subscribe(route.runId, {
477
+ after: route.after,
478
+ signal,
479
+ streams: route.streams === false || !grant.permissions.includes(_runlane_contracts_realtime.RunReadPermission.Streams) ? false : grant.streams
480
+ })) {
481
+ if (signal.aborted) return;
482
+ const projected = projectEvent(event, grant);
483
+ yield projected === void 0 ? {
484
+ event: "checkpoint",
485
+ cursor: event.cursor,
486
+ data: {}
487
+ } : {
488
+ event: projected.type,
489
+ cursor: projected.cursor,
490
+ data: projected
491
+ };
492
+ }
493
+ if (!signal.aborted) yield {
494
+ event: "end",
495
+ data: {}
496
+ };
497
+ return;
498
+ }
499
+ yield* namedFrames(source, route.runId, route.name, route.after, signal);
500
+ }
501
+ async function* namedFrames(source, runId, name, initial, signal) {
502
+ let after = initial;
503
+ let terminal = false;
504
+ while (!signal.aborted) {
505
+ const controller = new AbortController();
506
+ const waiting = source.lane?.runSignal?.wait({
507
+ environment: source.environment,
508
+ runId,
509
+ signal: AbortSignal.any([signal, controller.signal]),
510
+ timeoutMs: pollInterval
511
+ }).catch((cause) => {
512
+ throw new _runlane_contracts_realtime.RunlaneError({
513
+ code: _runlane_contracts_realtime.ErrorCode.AdapterContractViolation,
514
+ message: "Lane run signal wait must not reject.",
515
+ cause
516
+ });
517
+ });
518
+ waiting?.catch(() => void 0);
519
+ try {
520
+ const page = await source.runs.readStream(runId, name, {
521
+ after,
522
+ limit: _runlane_contracts_realtime.contractDefaults.pagination.defaultLimit
523
+ });
524
+ yield* namedPageFrames(page, after);
525
+ after = page.cursor;
526
+ if (page.hasMore) continue;
527
+ if (terminal && page.records.length === 0) {
528
+ yield {
529
+ event: "end",
530
+ data: {}
531
+ };
532
+ return;
533
+ }
534
+ terminal = await readNamedRunTerminal(source, runId);
535
+ if (terminal) continue;
536
+ if (page.records.length === 0) await (waiting ?? (0, _runlane_utilities_browser.waitForAbortableTimeout)(pollInterval, signal));
537
+ } finally {
538
+ controller.abort();
539
+ await waiting;
540
+ }
541
+ }
542
+ }
543
+ async function readNamedRunTerminal(source, runId) {
544
+ const run = await source.runs.get(runId);
545
+ if (run === void 0) throw new _runlane_contracts_realtime.RunlaneError({
546
+ code: _runlane_contracts_realtime.ErrorCode.RunNotFound,
547
+ message: "Run was not found.",
548
+ retryable: false
549
+ });
550
+ return _runlane_contracts_realtime.runStatusValues.isTerminal(run.status);
551
+ }
552
+ function* namedPageFrames(page, after) {
553
+ for (const [index, record] of page.records.entries()) {
554
+ const last = index === page.records.length - 1;
555
+ yield {
556
+ event: "stream",
557
+ ...last ? { cursor: page.cursor } : {},
558
+ data: {
559
+ ...last ? { cursor: page.cursor } : {},
560
+ record
561
+ }
562
+ };
563
+ }
564
+ if (page.records.length === 0 && page.cursor !== after) yield {
565
+ event: "checkpoint",
566
+ cursor: page.cursor,
567
+ data: {}
568
+ };
569
+ }
570
+ function publicError(cause, cursor) {
571
+ const checkpoint = cursor ?? (cause instanceof _runlane_contracts_realtime.RunlaneError ? _runlane_contracts_realtime.runCursorSchema.safeParse(cause.meta?.cursor).data : void 0);
572
+ const resume = checkpoint === void 0 ? {} : { cursor: checkpoint };
573
+ if (cause instanceof _runlane_contracts_realtime.RunlaneError) {
574
+ const code = _runlane_contracts_realtime.runRealtimeErrorCodeSchema.safeParse(cause.code);
575
+ if (code.success && code.data !== _runlane_contracts_realtime.ErrorCode.InternalError) return {
576
+ code: code.data,
577
+ message: cause.message,
578
+ retryable: cause.retryable,
579
+ ...resume
580
+ };
581
+ }
582
+ return {
583
+ code: _runlane_contracts_realtime.ErrorCode.InternalError,
584
+ message: "Realtime request failed.",
585
+ retryable: true,
586
+ ...resume
587
+ };
588
+ }
589
+ function publicSourceError(cause) {
590
+ const error = publicError(cause);
591
+ if (cause instanceof _runlane_contracts_realtime.RunlaneError) switch (cause.code) {
592
+ case _runlane_contracts_realtime.ErrorCode.ConfigurationInvalid:
593
+ case _runlane_contracts_realtime.ErrorCode.ValidationFailed: return {
594
+ ...error,
595
+ code: _runlane_contracts_realtime.ErrorCode.ValidationFailed,
596
+ message: "Realtime request is invalid.",
597
+ retryable: false
598
+ };
599
+ case _runlane_contracts_realtime.ErrorCode.RunCursorInvalid: return {
600
+ ...error,
601
+ code: _runlane_contracts_realtime.ErrorCode.RunCursorInvalid,
602
+ message: "Run cursor is invalid.",
603
+ retryable: false
604
+ };
605
+ case _runlane_contracts_realtime.ErrorCode.RunNotFound: return {
606
+ ...error,
607
+ code: _runlane_contracts_realtime.ErrorCode.RunNotFound,
608
+ message: "Run was not found.",
609
+ retryable: false
610
+ };
611
+ case _runlane_contracts_realtime.ErrorCode.OperationCancelled: return {
612
+ ...error,
613
+ code: _runlane_contracts_realtime.ErrorCode.Disconnected,
614
+ message: "Realtime runtime is closing; reconnect to resume.",
615
+ retryable: true
616
+ };
617
+ }
618
+ return error;
619
+ }
620
+ function httpStatus(code) {
621
+ if (code === _runlane_contracts_realtime.ErrorCode.AccessDenied) return 403;
622
+ if (code === _runlane_contracts_realtime.ErrorCode.RunNotFound) return 404;
623
+ if (code === _runlane_contracts_realtime.ErrorCode.ValidationFailed || code === _runlane_contracts_realtime.ErrorCode.RunCursorInvalid) return 400;
624
+ return 500;
625
+ }
626
+ var AccessDenialReason = /* @__PURE__ */ function(AccessDenialReason) {
627
+ AccessDenialReason["ApplicationDenied"] = "application_denied";
628
+ AccessDenialReason["AuthorizationHeader"] = "authorization_header";
629
+ AccessDenialReason["VerificationUnconfigured"] = "verification_unconfigured";
630
+ AccessDenialReason["TokenScope"] = "token_scope";
631
+ return AccessDenialReason;
632
+ }(AccessDenialReason || {});
633
+ const accessDenialReasonSchema = zod.enum(AccessDenialReason);
634
+ function accessDenied(reason = "application_denied") {
635
+ return new _runlane_contracts_realtime.RunlaneError({
636
+ code: _runlane_contracts_realtime.ErrorCode.AccessDenied,
637
+ message: "Run access denied.",
638
+ meta: { reason }
639
+ });
640
+ }
641
+ function invalidRequest(message, cause) {
642
+ return new _runlane_contracts_realtime.RunlaneError({
643
+ code: _runlane_contracts_realtime.ErrorCode.ValidationFailed,
644
+ message,
645
+ retryable: false,
646
+ cause
647
+ });
648
+ }
649
+ function invalidCursor() {
650
+ return new _runlane_contracts_realtime.RunlaneError({
651
+ code: _runlane_contracts_realtime.ErrorCode.RunCursorInvalid,
652
+ message: "Run cursor is invalid or conflicts with after.",
653
+ retryable: false
654
+ });
655
+ }
656
+ async function requestToken(request, runId, options, verification) {
657
+ const supplied = request.headers.get("authorization");
658
+ if (supplied === null) return void 0;
659
+ const match = /^Bearer ([a-z0-9_.-]+)$/i.exec(supplied);
660
+ if (match?.[1] === void 0) throw accessDenied("authorization_header");
661
+ if (verification === void 0) throw accessDenied("verification_unconfigured");
662
+ const token = await verifyRunToken(match[1], verification, options, new URL(request.url).pathname.endsWith("/events") ? "subscribe" : "read");
663
+ if (token.runId !== runId || token.environment.name !== options.runlane.environment.name) throw accessDenied("token_scope");
664
+ return token;
665
+ }
666
+ Object.defineProperty(exports, "ErrorCode", {
667
+ enumerable: true,
668
+ get: function() {
669
+ return _runlane_contracts_realtime.ErrorCode;
670
+ }
671
+ });
672
+ Object.defineProperty(exports, "RunReadPermission", {
673
+ enumerable: true,
674
+ get: function() {
675
+ return _runlane_contracts_realtime.RunReadPermission;
676
+ }
677
+ });
678
+ Object.defineProperty(exports, "RunlaneError", {
679
+ enumerable: true,
680
+ get: function() {
681
+ return _runlane_contracts_realtime.RunlaneError;
682
+ }
683
+ });
684
+ exports.createRunlaneRealtime = createRunlaneRealtime;
685
+
686
+ //# sourceMappingURL=index.cjs.map