@starcite/sdk 0.0.1 → 0.0.3
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/README.md +45 -1
- package/dist/index.cjs +317 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -2
- package/dist/index.d.ts +44 -2
- package/dist/index.js +317 -66
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -151,13 +151,51 @@ setTimeout(() => controller.abort(), 5000);
|
|
|
151
151
|
for await (const event of session.tail({
|
|
152
152
|
cursor: 0,
|
|
153
153
|
agent: "drafter",
|
|
154
|
+
reconnect: true,
|
|
155
|
+
reconnectDelayMs: 3000,
|
|
154
156
|
signal: controller.signal,
|
|
155
157
|
})) {
|
|
156
158
|
console.log(event);
|
|
157
159
|
}
|
|
158
160
|
```
|
|
159
161
|
|
|
160
|
-
`tail()` replays `seq > cursor
|
|
162
|
+
`tail()` replays `seq > cursor`, streams live events, and automatically reconnects
|
|
163
|
+
on transport failures while resuming from the last observed `seq`.
|
|
164
|
+
|
|
165
|
+
- Set `reconnect: false` to disable automatic reconnect behavior.
|
|
166
|
+
- By default, reconnect retries continue until the stream is aborted or closes gracefully.
|
|
167
|
+
- Use `reconnectDelayMs` to control retry cadence for spotty networks.
|
|
168
|
+
|
|
169
|
+
## Browser Restart Resilience
|
|
170
|
+
|
|
171
|
+
`tail()` reconnects robustly for transport failures, but browser refresh/crash
|
|
172
|
+
resets in-memory state. Persist your last processed `seq` and restart from it.
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
const sessionId = "ses_demo";
|
|
176
|
+
const cursorKey = `starcite:${sessionId}:lastSeq`;
|
|
177
|
+
|
|
178
|
+
const rawCursor = localStorage.getItem(cursorKey) ?? "0";
|
|
179
|
+
let lastSeq = Number.parseInt(rawCursor, 10);
|
|
180
|
+
|
|
181
|
+
if (!Number.isInteger(lastSeq) || lastSeq < 0) {
|
|
182
|
+
lastSeq = 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for await (const event of client.session(sessionId).tail({
|
|
186
|
+
cursor: lastSeq,
|
|
187
|
+
reconnect: true,
|
|
188
|
+
reconnectDelayMs: 3000,
|
|
189
|
+
})) {
|
|
190
|
+
// Process event first, then persist cursor when your side effects succeed.
|
|
191
|
+
await renderOrStore(event);
|
|
192
|
+
lastSeq = event.seq;
|
|
193
|
+
localStorage.setItem(cursorKey, `${lastSeq}`);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
This pattern protects against missed events across browser restarts. Design your
|
|
198
|
+
event handler to be idempotent by `seq` to safely tolerate replays.
|
|
161
199
|
|
|
162
200
|
## Error Handling
|
|
163
201
|
|
|
@@ -212,6 +250,12 @@ bun run --cwd packages/typescript-sdk build
|
|
|
212
250
|
bun run --cwd packages/typescript-sdk test
|
|
213
251
|
```
|
|
214
252
|
|
|
253
|
+
Optional reconnect soak test (runs ~40s, disabled by default):
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
STARCITE_SDK_RUN_SOAK=1 bun run --cwd packages/typescript-sdk test -- test/client.reconnect.integration.test.ts
|
|
257
|
+
```
|
|
258
|
+
|
|
215
259
|
## Links
|
|
216
260
|
|
|
217
261
|
- Product docs and examples: https://starcite.ai
|
package/dist/index.cjs
CHANGED
|
@@ -66,10 +66,17 @@ var StarciteConnectionError = class extends StarciteError {
|
|
|
66
66
|
// src/types.ts
|
|
67
67
|
var import_zod = require("zod");
|
|
68
68
|
var ArbitraryObjectSchema = import_zod.z.record(import_zod.z.unknown());
|
|
69
|
+
var CreatorTypeSchema = import_zod.z.union([import_zod.z.literal("user"), import_zod.z.literal("agent")]);
|
|
70
|
+
var SessionCreatorPrincipalSchema = import_zod.z.object({
|
|
71
|
+
tenant_id: import_zod.z.string().min(1),
|
|
72
|
+
id: import_zod.z.string().min(1),
|
|
73
|
+
type: CreatorTypeSchema
|
|
74
|
+
});
|
|
69
75
|
var CreateSessionInputSchema = import_zod.z.object({
|
|
70
76
|
id: import_zod.z.string().optional(),
|
|
71
77
|
title: import_zod.z.string().optional(),
|
|
72
|
-
metadata: ArbitraryObjectSchema.optional()
|
|
78
|
+
metadata: ArbitraryObjectSchema.optional(),
|
|
79
|
+
creator_principal: SessionCreatorPrincipalSchema.optional()
|
|
73
80
|
});
|
|
74
81
|
var SessionRecordSchema = import_zod.z.object({
|
|
75
82
|
id: import_zod.z.string(),
|
|
@@ -147,6 +154,12 @@ var StarciteErrorPayloadSchema = import_zod.z.object({
|
|
|
147
154
|
var DEFAULT_BASE_URL = typeof process !== "undefined" && process.env.STARCITE_BASE_URL ? process.env.STARCITE_BASE_URL : "http://localhost:4000";
|
|
148
155
|
var TRAILING_SLASHES_REGEX = /\/+$/;
|
|
149
156
|
var BEARER_PREFIX_REGEX = /^bearer\s+/i;
|
|
157
|
+
var DEFAULT_TAIL_RECONNECT_DELAY_MS = 3e3;
|
|
158
|
+
var CATCH_UP_IDLE_MS = 1e3;
|
|
159
|
+
var NORMAL_WEBSOCKET_CLOSE_CODE = 1e3;
|
|
160
|
+
var SERVICE_TOKEN_SUB_ORG_PREFIX = "org:";
|
|
161
|
+
var SERVICE_TOKEN_SUB_AGENT_PREFIX = "agent:";
|
|
162
|
+
var SERVICE_TOKEN_SUB_USER_PREFIX = "user:";
|
|
150
163
|
var TailFrameSchema = import_zod2.z.string().transform((frame, context) => {
|
|
151
164
|
try {
|
|
152
165
|
return JSON.parse(frame);
|
|
@@ -267,6 +280,105 @@ function formatAuthorizationHeader(apiKey) {
|
|
|
267
280
|
}
|
|
268
281
|
return `Bearer ${normalized}`;
|
|
269
282
|
}
|
|
283
|
+
function firstNonEmptyString(value) {
|
|
284
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
285
|
+
}
|
|
286
|
+
function parseJwtSegment(segment) {
|
|
287
|
+
const base64 = segment.replace(/-/g, "+").replace(/_/g, "/").padEnd(segment.length + (4 - segment.length % 4) % 4, "=");
|
|
288
|
+
try {
|
|
289
|
+
if (typeof atob === "function") {
|
|
290
|
+
return atob(base64);
|
|
291
|
+
}
|
|
292
|
+
if (typeof Buffer !== "undefined") {
|
|
293
|
+
return Buffer.from(base64, "base64").toString("utf8");
|
|
294
|
+
}
|
|
295
|
+
} catch {
|
|
296
|
+
return void 0;
|
|
297
|
+
}
|
|
298
|
+
return void 0;
|
|
299
|
+
}
|
|
300
|
+
function parseJwtClaims(apiKey) {
|
|
301
|
+
const token = apiKey.replace(BEARER_PREFIX_REGEX, "").trim();
|
|
302
|
+
const parts = token.split(".");
|
|
303
|
+
if (parts.length !== 3) {
|
|
304
|
+
return void 0;
|
|
305
|
+
}
|
|
306
|
+
const [, payloadSegment] = parts;
|
|
307
|
+
if (payloadSegment === void 0) {
|
|
308
|
+
return void 0;
|
|
309
|
+
}
|
|
310
|
+
const payload = parseJwtSegment(payloadSegment);
|
|
311
|
+
if (payload === void 0) {
|
|
312
|
+
return void 0;
|
|
313
|
+
}
|
|
314
|
+
try {
|
|
315
|
+
const decoded = JSON.parse(payload);
|
|
316
|
+
return decoded !== null && typeof decoded === "object" ? decoded : void 0;
|
|
317
|
+
} catch {
|
|
318
|
+
return void 0;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function parseClaimStrings(source, keys) {
|
|
322
|
+
for (const key of keys) {
|
|
323
|
+
const value = firstNonEmptyString(source[key]);
|
|
324
|
+
if (value) {
|
|
325
|
+
return value;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return void 0;
|
|
329
|
+
}
|
|
330
|
+
function parseActorIdentityFromSubject(subject) {
|
|
331
|
+
if (subject.startsWith(SERVICE_TOKEN_SUB_AGENT_PREFIX)) {
|
|
332
|
+
return { id: subject, type: "agent" };
|
|
333
|
+
}
|
|
334
|
+
if (subject.startsWith(SERVICE_TOKEN_SUB_USER_PREFIX)) {
|
|
335
|
+
return { id: subject, type: "user" };
|
|
336
|
+
}
|
|
337
|
+
return void 0;
|
|
338
|
+
}
|
|
339
|
+
function parseTenantIdFromSubject(subject) {
|
|
340
|
+
const actorIdentity = parseActorIdentityFromSubject(subject);
|
|
341
|
+
if (actorIdentity !== void 0) {
|
|
342
|
+
return "";
|
|
343
|
+
}
|
|
344
|
+
if (subject.startsWith(SERVICE_TOKEN_SUB_ORG_PREFIX)) {
|
|
345
|
+
return subject.slice(SERVICE_TOKEN_SUB_ORG_PREFIX.length).trim();
|
|
346
|
+
}
|
|
347
|
+
return subject;
|
|
348
|
+
}
|
|
349
|
+
function parseCreatorPrincipalFromClaims(claims) {
|
|
350
|
+
const subject = firstNonEmptyString(claims.sub);
|
|
351
|
+
const explicitPrincipal = claims.principal && typeof claims.principal === "object" ? claims.principal : void 0;
|
|
352
|
+
const mergedClaims = explicitPrincipal ? { ...claims, ...explicitPrincipal } : claims;
|
|
353
|
+
const actorFromSubject = subject ? parseActorIdentityFromSubject(subject) : void 0;
|
|
354
|
+
const principalTypeFromClaims = parseClaimStrings(mergedClaims, [
|
|
355
|
+
"principal_type",
|
|
356
|
+
"principalType",
|
|
357
|
+
"type"
|
|
358
|
+
]);
|
|
359
|
+
const tenantId = parseClaimStrings(mergedClaims, ["tenant_id", "tenantId"]);
|
|
360
|
+
const rawPrincipalId = parseClaimStrings(mergedClaims, [
|
|
361
|
+
"principal_id",
|
|
362
|
+
"principalId",
|
|
363
|
+
"id",
|
|
364
|
+
"sub"
|
|
365
|
+
]);
|
|
366
|
+
const actorFromRawId = rawPrincipalId ? parseActorIdentityFromSubject(rawPrincipalId) : void 0;
|
|
367
|
+
const principal = {
|
|
368
|
+
tenant_id: tenantId ?? (subject ? parseTenantIdFromSubject(subject) : ""),
|
|
369
|
+
id: rawPrincipalId ?? actorFromSubject?.id ?? "",
|
|
370
|
+
type: principalTypeFromClaims === "agent" || principalTypeFromClaims === "user" ? principalTypeFromClaims : actorFromSubject?.type ?? actorFromRawId?.type ?? "user"
|
|
371
|
+
};
|
|
372
|
+
if (principal.tenant_id.length === 0 || principal.id.length === 0 || principal.type.length === 0) {
|
|
373
|
+
return void 0;
|
|
374
|
+
}
|
|
375
|
+
const result = SessionCreatorPrincipalSchema.safeParse(principal);
|
|
376
|
+
return result.success ? result.data : void 0;
|
|
377
|
+
}
|
|
378
|
+
function parseCreatorPrincipalFromClaimsSafe(apiKey) {
|
|
379
|
+
const claims = parseJwtClaims(apiKey);
|
|
380
|
+
return claims ? parseCreatorPrincipalFromClaims(claims) : void 0;
|
|
381
|
+
}
|
|
270
382
|
function parseEventFrame(data) {
|
|
271
383
|
const result = TailFrameSchema.safeParse(data);
|
|
272
384
|
if (!result.success) {
|
|
@@ -281,6 +393,58 @@ function getEventData(event) {
|
|
|
281
393
|
}
|
|
282
394
|
return void 0;
|
|
283
395
|
}
|
|
396
|
+
function getCloseCode(event) {
|
|
397
|
+
if (event && typeof event === "object" && "code" in event) {
|
|
398
|
+
const code = event.code;
|
|
399
|
+
return typeof code === "number" ? code : void 0;
|
|
400
|
+
}
|
|
401
|
+
return void 0;
|
|
402
|
+
}
|
|
403
|
+
function getCloseReason(event) {
|
|
404
|
+
if (event && typeof event === "object" && "reason" in event) {
|
|
405
|
+
const reason = event.reason;
|
|
406
|
+
return typeof reason === "string" && reason.length > 0 ? reason : void 0;
|
|
407
|
+
}
|
|
408
|
+
return void 0;
|
|
409
|
+
}
|
|
410
|
+
function describeClose(code, reason) {
|
|
411
|
+
const codeText = `code ${typeof code === "number" ? code : "unknown"}`;
|
|
412
|
+
return reason ? `${codeText}, reason '${reason}'` : codeText;
|
|
413
|
+
}
|
|
414
|
+
async function waitForDelay(ms, signal) {
|
|
415
|
+
if (ms <= 0) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
await new Promise((resolve) => {
|
|
419
|
+
let settled = false;
|
|
420
|
+
const timeout = setTimeout(() => {
|
|
421
|
+
if (settled) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
settled = true;
|
|
425
|
+
if (signal) {
|
|
426
|
+
signal.removeEventListener("abort", onAbort);
|
|
427
|
+
}
|
|
428
|
+
resolve();
|
|
429
|
+
}, ms);
|
|
430
|
+
const onAbort = () => {
|
|
431
|
+
if (settled) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
settled = true;
|
|
435
|
+
clearTimeout(timeout);
|
|
436
|
+
signal?.removeEventListener("abort", onAbort);
|
|
437
|
+
resolve();
|
|
438
|
+
};
|
|
439
|
+
if (signal) {
|
|
440
|
+
if (signal.aborted) {
|
|
441
|
+
onAbort();
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
284
448
|
function agentFromActor(actor) {
|
|
285
449
|
if (actor.startsWith("agent:")) {
|
|
286
450
|
return actor.slice("agent:".length);
|
|
@@ -350,6 +514,7 @@ var StarciteSession = class {
|
|
|
350
514
|
var StarciteClient = class {
|
|
351
515
|
/** Normalized API base URL ending with `/v1`. */
|
|
352
516
|
baseUrl;
|
|
517
|
+
inferredCreatorPrincipal;
|
|
353
518
|
websocketBaseUrl;
|
|
354
519
|
fetchFn;
|
|
355
520
|
headers;
|
|
@@ -363,10 +528,9 @@ var StarciteClient = class {
|
|
|
363
528
|
this.fetchFn = options.fetch ?? defaultFetch;
|
|
364
529
|
this.headers = new Headers(options.headers);
|
|
365
530
|
if (options.apiKey !== void 0) {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
);
|
|
531
|
+
const authorization = formatAuthorizationHeader(options.apiKey);
|
|
532
|
+
this.headers.set("authorization", authorization);
|
|
533
|
+
this.inferredCreatorPrincipal = parseCreatorPrincipalFromClaimsSafe(authorization);
|
|
370
534
|
}
|
|
371
535
|
this.websocketFactory = options.websocketFactory ?? defaultWebSocketFactory;
|
|
372
536
|
}
|
|
@@ -387,7 +551,10 @@ var StarciteClient = class {
|
|
|
387
551
|
* Creates a new session and returns the raw session record.
|
|
388
552
|
*/
|
|
389
553
|
createSession(input = {}) {
|
|
390
|
-
const payload = CreateSessionInputSchema.parse(
|
|
554
|
+
const payload = CreateSessionInputSchema.parse({
|
|
555
|
+
...input,
|
|
556
|
+
creator_principal: input.creator_principal ?? this.inferredCreatorPrincipal
|
|
557
|
+
});
|
|
391
558
|
return this.request(
|
|
392
559
|
"/sessions",
|
|
393
560
|
{
|
|
@@ -452,77 +619,161 @@ var StarciteClient = class {
|
|
|
452
619
|
/**
|
|
453
620
|
* Opens a WebSocket tail stream and yields raw events.
|
|
454
621
|
*/
|
|
622
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: single-loop reconnect state machine is intentionally explicit for stream correctness.
|
|
455
623
|
async *tailRawEvents(sessionId, options = {}) {
|
|
456
|
-
const
|
|
457
|
-
const
|
|
458
|
-
|
|
624
|
+
const initialCursor = options.cursor ?? 0;
|
|
625
|
+
const follow = options.follow ?? true;
|
|
626
|
+
const reconnectEnabled = follow ? options.reconnect ?? true : false;
|
|
627
|
+
const reconnectDelayMs = options.reconnectDelayMs ?? DEFAULT_TAIL_RECONNECT_DELAY_MS;
|
|
628
|
+
if (!Number.isInteger(initialCursor) || initialCursor < 0) {
|
|
459
629
|
throw new StarciteError("tail() cursor must be a non-negative integer");
|
|
460
630
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const authorization = this.headers.get("authorization");
|
|
466
|
-
if (authorization) {
|
|
467
|
-
websocketHeaders.set("authorization", authorization);
|
|
631
|
+
if (!Number.isFinite(reconnectDelayMs) || reconnectDelayMs < 0) {
|
|
632
|
+
throw new StarciteError(
|
|
633
|
+
"tail() reconnectDelayMs must be a non-negative number"
|
|
634
|
+
);
|
|
468
635
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
|
|
636
|
+
let cursor = initialCursor;
|
|
637
|
+
while (true) {
|
|
638
|
+
if (options.signal?.aborted) {
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
const wsUrl = `${this.websocketBaseUrl}/sessions/${encodeURIComponent(
|
|
642
|
+
sessionId
|
|
643
|
+
)}/tail?cursor=${cursor}`;
|
|
644
|
+
const websocketHeaders = new Headers();
|
|
645
|
+
const authorization = this.headers.get("authorization");
|
|
646
|
+
if (authorization) {
|
|
647
|
+
websocketHeaders.set("authorization", authorization);
|
|
648
|
+
}
|
|
649
|
+
let socket;
|
|
476
650
|
try {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
651
|
+
socket = this.websocketFactory(
|
|
652
|
+
wsUrl,
|
|
653
|
+
hasAnyHeaders(websocketHeaders) ? {
|
|
654
|
+
headers: websocketHeaders
|
|
655
|
+
} : void 0
|
|
656
|
+
);
|
|
482
657
|
} catch (error) {
|
|
483
|
-
|
|
658
|
+
const rootCause = toError(error).message;
|
|
659
|
+
if (!reconnectEnabled || options.signal?.aborted) {
|
|
660
|
+
throw new StarciteConnectionError(
|
|
661
|
+
`Tail connection failed for session '${sessionId}': ${rootCause}`
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
await waitForDelay(reconnectDelayMs, options.signal);
|
|
665
|
+
continue;
|
|
484
666
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
)
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
667
|
+
const queue = new AsyncQueue();
|
|
668
|
+
let sawTransportError = false;
|
|
669
|
+
let closeCode;
|
|
670
|
+
let closeReason;
|
|
671
|
+
let abortRequested = false;
|
|
672
|
+
let catchUpTimer = null;
|
|
673
|
+
const resetCatchUpTimer = () => {
|
|
674
|
+
if (!follow) {
|
|
675
|
+
if (catchUpTimer) {
|
|
676
|
+
clearTimeout(catchUpTimer);
|
|
677
|
+
}
|
|
678
|
+
catchUpTimer = setTimeout(() => {
|
|
679
|
+
queue.close();
|
|
680
|
+
}, CATCH_UP_IDLE_MS);
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
const onMessage = (event) => {
|
|
684
|
+
try {
|
|
685
|
+
const parsed = parseEventFrame(getEventData(event));
|
|
686
|
+
cursor = Math.max(cursor, parsed.seq);
|
|
687
|
+
if (options.agent && agentFromActor(parsed.actor) !== options.agent) {
|
|
688
|
+
resetCatchUpTimer();
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
queue.push(parsed);
|
|
692
|
+
resetCatchUpTimer();
|
|
693
|
+
} catch (error) {
|
|
694
|
+
queue.fail(error);
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
const onError = () => {
|
|
698
|
+
sawTransportError = true;
|
|
699
|
+
if (catchUpTimer) {
|
|
700
|
+
clearTimeout(catchUpTimer);
|
|
701
|
+
}
|
|
702
|
+
queue.close();
|
|
703
|
+
};
|
|
704
|
+
const onClose = (event) => {
|
|
705
|
+
closeCode = getCloseCode(event);
|
|
706
|
+
closeReason = getCloseReason(event);
|
|
707
|
+
if (catchUpTimer) {
|
|
708
|
+
clearTimeout(catchUpTimer);
|
|
709
|
+
}
|
|
710
|
+
queue.close();
|
|
711
|
+
};
|
|
712
|
+
const onAbort = () => {
|
|
713
|
+
abortRequested = true;
|
|
714
|
+
if (catchUpTimer) {
|
|
715
|
+
clearTimeout(catchUpTimer);
|
|
716
|
+
}
|
|
717
|
+
queue.close();
|
|
718
|
+
socket.close(NORMAL_WEBSOCKET_CLOSE_CODE, "aborted");
|
|
719
|
+
};
|
|
720
|
+
const onOpen = () => {
|
|
721
|
+
resetCatchUpTimer();
|
|
722
|
+
};
|
|
723
|
+
socket.addEventListener("open", onOpen);
|
|
724
|
+
socket.addEventListener("message", onMessage);
|
|
725
|
+
socket.addEventListener("error", onError);
|
|
726
|
+
socket.addEventListener("close", onClose);
|
|
727
|
+
if (options.signal) {
|
|
728
|
+
if (options.signal.aborted) {
|
|
729
|
+
onAbort();
|
|
730
|
+
} else {
|
|
731
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
732
|
+
}
|
|
508
733
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
734
|
+
let iterationError = null;
|
|
735
|
+
try {
|
|
736
|
+
while (true) {
|
|
737
|
+
const next = await queue.next();
|
|
738
|
+
if (next.done) {
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
yield next.value;
|
|
742
|
+
}
|
|
743
|
+
} catch (error) {
|
|
744
|
+
iterationError = toError(error);
|
|
745
|
+
} finally {
|
|
746
|
+
if (catchUpTimer) {
|
|
747
|
+
clearTimeout(catchUpTimer);
|
|
748
|
+
}
|
|
749
|
+
socket.removeEventListener("open", onOpen);
|
|
750
|
+
socket.removeEventListener("message", onMessage);
|
|
751
|
+
socket.removeEventListener("error", onError);
|
|
752
|
+
socket.removeEventListener("close", onClose);
|
|
753
|
+
if (options.signal) {
|
|
754
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
515
755
|
}
|
|
516
|
-
|
|
756
|
+
socket.close(NORMAL_WEBSOCKET_CLOSE_CODE, "finished");
|
|
517
757
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
758
|
+
if (iterationError) {
|
|
759
|
+
throw iterationError;
|
|
760
|
+
}
|
|
761
|
+
if (abortRequested || options.signal?.aborted || !follow) {
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
const gracefullyClosed = !sawTransportError && closeCode === NORMAL_WEBSOCKET_CLOSE_CODE;
|
|
765
|
+
if (gracefullyClosed) {
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
if (!reconnectEnabled) {
|
|
769
|
+
throw new StarciteConnectionError(
|
|
770
|
+
`Tail connection dropped for session '${sessionId}' (${describeClose(
|
|
771
|
+
closeCode,
|
|
772
|
+
closeReason
|
|
773
|
+
)})`
|
|
774
|
+
);
|
|
524
775
|
}
|
|
525
|
-
|
|
776
|
+
await waitForDelay(reconnectDelayMs, options.signal);
|
|
526
777
|
}
|
|
527
778
|
}
|
|
528
779
|
/**
|