@starcite/sdk 0.0.5 → 0.0.7
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 +15 -15
- package/dist/index.cjs +371 -145
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -84
- package/dist/index.d.ts +138 -84
- package/dist/index.js +369 -142
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,8 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
|
|
34
|
-
LocalStorageCursorStore: () => LocalStorageCursorStore,
|
|
33
|
+
LocalStorageSessionStore: () => LocalStorageSessionStore,
|
|
35
34
|
MemoryStore: () => MemoryStore,
|
|
36
35
|
SessionLogConflictError: () => SessionLogConflictError,
|
|
37
36
|
SessionLogGapError: () => SessionLogGapError,
|
|
@@ -45,7 +44,7 @@ __export(index_exports, {
|
|
|
45
44
|
StarciteSession: () => StarciteSession,
|
|
46
45
|
StarciteTailError: () => StarciteTailError,
|
|
47
46
|
StarciteTokenExpiredError: () => StarciteTokenExpiredError,
|
|
48
|
-
|
|
47
|
+
WebStorageSessionStore: () => WebStorageSessionStore
|
|
49
48
|
});
|
|
50
49
|
module.exports = __toCommonJS(index_exports);
|
|
51
50
|
|
|
@@ -129,13 +128,14 @@ function inferIdentityFromApiKey(apiKey) {
|
|
|
129
128
|
const claims = ApiKeyClaimsSchema.parse((0, import_jose.decodeJwt)(apiKey));
|
|
130
129
|
const id = claims.principal_id ?? claims.sub;
|
|
131
130
|
const tenantId = claims.tenant_id;
|
|
132
|
-
|
|
131
|
+
const type = claims.principal_type ?? "user";
|
|
132
|
+
if (!(tenantId && id)) {
|
|
133
133
|
return void 0;
|
|
134
134
|
}
|
|
135
135
|
return new StarciteIdentity({
|
|
136
136
|
tenantId,
|
|
137
137
|
id,
|
|
138
|
-
type
|
|
138
|
+
type
|
|
139
139
|
});
|
|
140
140
|
}
|
|
141
141
|
function decodeSessionToken(token) {
|
|
@@ -336,7 +336,7 @@ var SessionLog = class {
|
|
|
336
336
|
this.enforceRetention();
|
|
337
337
|
return true;
|
|
338
338
|
}
|
|
339
|
-
|
|
339
|
+
state(syncing) {
|
|
340
340
|
return {
|
|
341
341
|
events: this.history.slice(),
|
|
342
342
|
lastSeq: this.appliedSeq,
|
|
@@ -939,7 +939,7 @@ var TailStream = class {
|
|
|
939
939
|
this.follow = follow;
|
|
940
940
|
this.shouldReconnect = follow ? opts.reconnect ?? true : false;
|
|
941
941
|
this.catchUpIdleMs = opts.catchUpIdleMs ?? 1e3;
|
|
942
|
-
this.connectionTimeoutMs = opts.connectionTimeoutMs ??
|
|
942
|
+
this.connectionTimeoutMs = opts.connectionTimeoutMs ?? 12e3;
|
|
943
943
|
this.inactivityTimeoutMs = opts.inactivityTimeoutMs;
|
|
944
944
|
this.maxBufferedBatches = opts.maxBufferedBatches ?? 1024;
|
|
945
945
|
this.signal = opts.signal;
|
|
@@ -1310,8 +1310,10 @@ var StarciteSession = class {
|
|
|
1310
1310
|
store;
|
|
1311
1311
|
lifecycle = new import_eventemitter33.default();
|
|
1312
1312
|
eventSubscriptions = /* @__PURE__ */ new Map();
|
|
1313
|
+
appendTask = Promise.resolve();
|
|
1313
1314
|
liveSyncController;
|
|
1314
1315
|
liveSyncTask;
|
|
1316
|
+
liveSyncCatchUpActive = false;
|
|
1315
1317
|
constructor(options) {
|
|
1316
1318
|
this.id = options.id;
|
|
1317
1319
|
this.token = options.token;
|
|
@@ -1321,8 +1323,8 @@ var StarciteSession = class {
|
|
|
1321
1323
|
this.producerId = crypto.randomUUID();
|
|
1322
1324
|
this.store = options.store;
|
|
1323
1325
|
this.log = new SessionLog(options.logOptions);
|
|
1324
|
-
const storedState = this.store
|
|
1325
|
-
if (storedState) {
|
|
1326
|
+
const storedState = this.store?.load(this.id);
|
|
1327
|
+
if (storedState !== void 0) {
|
|
1326
1328
|
this.log.hydrate(storedState);
|
|
1327
1329
|
}
|
|
1328
1330
|
}
|
|
@@ -1331,28 +1333,35 @@ var StarciteSession = class {
|
|
|
1331
1333
|
*
|
|
1332
1334
|
* The SDK manages `actor`, `producer_id`, and `producer_seq` automatically.
|
|
1333
1335
|
*/
|
|
1334
|
-
|
|
1336
|
+
append(input, options) {
|
|
1335
1337
|
const parsed = SessionAppendInputSchema.parse(input);
|
|
1336
|
-
this.
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1338
|
+
const runAppend = this.appendTask.then(async () => {
|
|
1339
|
+
this.producerSeq += 1;
|
|
1340
|
+
const result = await this.appendRaw(
|
|
1341
|
+
{
|
|
1342
|
+
type: parsed.type ?? "content",
|
|
1343
|
+
payload: parsed.payload ?? { text: parsed.text },
|
|
1344
|
+
actor: parsed.actor ?? this.identity.toActor(),
|
|
1345
|
+
producer_id: this.producerId,
|
|
1346
|
+
producer_seq: this.producerSeq,
|
|
1347
|
+
source: parsed.source ?? "agent",
|
|
1348
|
+
metadata: parsed.metadata,
|
|
1349
|
+
refs: parsed.refs,
|
|
1350
|
+
idempotency_key: parsed.idempotencyKey,
|
|
1351
|
+
expected_seq: parsed.expectedSeq
|
|
1352
|
+
},
|
|
1353
|
+
options
|
|
1354
|
+
);
|
|
1355
|
+
return {
|
|
1356
|
+
seq: result.seq,
|
|
1357
|
+
deduped: result.deduped
|
|
1358
|
+
};
|
|
1359
|
+
});
|
|
1360
|
+
this.appendTask = runAppend.then(
|
|
1361
|
+
() => void 0,
|
|
1362
|
+
() => void 0
|
|
1351
1363
|
);
|
|
1352
|
-
return
|
|
1353
|
-
seq: result.seq,
|
|
1354
|
-
deduped: result.deduped
|
|
1355
|
-
};
|
|
1364
|
+
return runAppend;
|
|
1356
1365
|
}
|
|
1357
1366
|
/**
|
|
1358
1367
|
* Appends a raw event payload as-is. Caller manages all fields.
|
|
@@ -1369,11 +1378,33 @@ var StarciteSession = class {
|
|
|
1369
1378
|
AppendEventResponseSchema
|
|
1370
1379
|
);
|
|
1371
1380
|
}
|
|
1372
|
-
on(eventName, listener) {
|
|
1381
|
+
on(eventName, listener, options) {
|
|
1373
1382
|
if (eventName === "event") {
|
|
1374
1383
|
const eventListener = listener;
|
|
1375
1384
|
if (!this.eventSubscriptions.has(eventListener)) {
|
|
1376
|
-
const
|
|
1385
|
+
const eventOptions = options;
|
|
1386
|
+
const replay = eventOptions?.replay ?? true;
|
|
1387
|
+
const replayCutoffSeq = replay ? this.log.lastSeq : -1;
|
|
1388
|
+
const schema = eventOptions?.schema;
|
|
1389
|
+
const dispatch = (event) => {
|
|
1390
|
+
const parsedEvent = this.parseOnEvent(event, schema);
|
|
1391
|
+
if (!parsedEvent) {
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
const classifiedContext = this.resolveEventContext(
|
|
1395
|
+
event.seq,
|
|
1396
|
+
replayCutoffSeq,
|
|
1397
|
+
this.liveSyncCatchUpActive
|
|
1398
|
+
);
|
|
1399
|
+
try {
|
|
1400
|
+
this.observeEventListenerResult(
|
|
1401
|
+
eventListener(parsedEvent, classifiedContext)
|
|
1402
|
+
);
|
|
1403
|
+
} catch (error) {
|
|
1404
|
+
this.emitStreamError(error);
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
const unsubscribe = this.log.subscribe(dispatch, { replay });
|
|
1377
1408
|
this.eventSubscriptions.set(eventListener, unsubscribe);
|
|
1378
1409
|
}
|
|
1379
1410
|
this.ensureLiveSync();
|
|
@@ -1435,77 +1466,195 @@ var StarciteSession = class {
|
|
|
1435
1466
|
this.persistLogState();
|
|
1436
1467
|
}
|
|
1437
1468
|
/**
|
|
1438
|
-
* Returns a stable
|
|
1469
|
+
* Returns a stable view of the current canonical in-memory log state.
|
|
1439
1470
|
*/
|
|
1440
|
-
|
|
1441
|
-
return this.log.
|
|
1471
|
+
state() {
|
|
1472
|
+
return this.log.state(this.liveSyncTask !== void 0);
|
|
1442
1473
|
}
|
|
1443
1474
|
/**
|
|
1444
|
-
*
|
|
1475
|
+
* Returns the retained canonical event list.
|
|
1445
1476
|
*/
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
for (const event of batch) {
|
|
1449
|
-
await onEvent(event);
|
|
1450
|
-
}
|
|
1451
|
-
}, options);
|
|
1477
|
+
events() {
|
|
1478
|
+
return this.log.events;
|
|
1452
1479
|
}
|
|
1453
1480
|
/**
|
|
1454
|
-
* Streams
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
await new TailStream({
|
|
1458
|
-
sessionId: this.id,
|
|
1459
|
-
token: this.token,
|
|
1460
|
-
websocketBaseUrl: this.transport.websocketBaseUrl,
|
|
1461
|
-
websocketFactory: this.transport.websocketFactory,
|
|
1462
|
-
options
|
|
1463
|
-
}).subscribe(onBatch);
|
|
1464
|
-
}
|
|
1465
|
-
/**
|
|
1466
|
-
* Durably consumes events and checkpoints `event.seq` after each successful handler invocation.
|
|
1481
|
+
* Streams canonical events as an async iterator.
|
|
1482
|
+
*
|
|
1483
|
+
* Replay semantics and schema validation mirror `session.on("event", ...)`.
|
|
1467
1484
|
*/
|
|
1468
|
-
|
|
1469
|
-
const {
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
+
tail(options = {}) {
|
|
1486
|
+
const { replay = true, schema, ...tailOptions } = options;
|
|
1487
|
+
const replayCutoffSeq = replay ? this.log.lastSeq : -1;
|
|
1488
|
+
const startCursor = tailOptions.cursor ?? this.log.lastSeq;
|
|
1489
|
+
const session = this;
|
|
1490
|
+
const parseEvent = (event) => session.parseTailEvent(event, schema);
|
|
1491
|
+
return {
|
|
1492
|
+
async *[Symbol.asyncIterator]() {
|
|
1493
|
+
if (replay) {
|
|
1494
|
+
for (const replayEvent of session.log.events) {
|
|
1495
|
+
yield {
|
|
1496
|
+
event: parseEvent(replayEvent),
|
|
1497
|
+
context: { phase: "replay", replayed: true }
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
yield* session.iterateLiveTail({
|
|
1502
|
+
parseEvent,
|
|
1503
|
+
replayCutoffSeq,
|
|
1504
|
+
startCursor,
|
|
1505
|
+
tailOptions
|
|
1506
|
+
});
|
|
1485
1507
|
}
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
parseOnEvent(event, schema) {
|
|
1511
|
+
if (!schema) {
|
|
1512
|
+
return event;
|
|
1486
1513
|
}
|
|
1487
|
-
|
|
1514
|
+
try {
|
|
1515
|
+
return schema.parse(event);
|
|
1516
|
+
} catch (error) {
|
|
1517
|
+
this.emitStreamError(
|
|
1518
|
+
new StarciteError(
|
|
1519
|
+
`session.on("event") schema validation failed for session '${this.id}': ${error instanceof Error ? error.message : String(error)}`
|
|
1520
|
+
)
|
|
1521
|
+
);
|
|
1522
|
+
return void 0;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
parseTailEvent(event, schema) {
|
|
1526
|
+
if (!schema) {
|
|
1527
|
+
return event;
|
|
1528
|
+
}
|
|
1529
|
+
try {
|
|
1530
|
+
return schema.parse(event);
|
|
1531
|
+
} catch (error) {
|
|
1532
|
+
throw new StarciteError(
|
|
1533
|
+
`session.tail() schema validation failed for session '${this.id}': ${error instanceof Error ? error.message : String(error)}`
|
|
1534
|
+
);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
resolveEventContext(eventSeq, replayCutoffSeq, forceReplay = false) {
|
|
1538
|
+
const replayed = forceReplay || eventSeq <= replayCutoffSeq;
|
|
1539
|
+
return replayed ? { phase: "replay", replayed: true } : { phase: "live", replayed: false };
|
|
1540
|
+
}
|
|
1541
|
+
observeEventListenerResult(result) {
|
|
1542
|
+
Promise.resolve(result).catch((error) => {
|
|
1543
|
+
this.emitStreamError(error);
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
createTailAbortController(outerSignal) {
|
|
1547
|
+
const controller = new AbortController();
|
|
1548
|
+
if (!outerSignal) {
|
|
1549
|
+
return { controller, detach: () => void 0 };
|
|
1550
|
+
}
|
|
1551
|
+
const abortFromOuterSignal = () => {
|
|
1552
|
+
controller.abort(outerSignal.reason);
|
|
1553
|
+
};
|
|
1554
|
+
if (outerSignal.aborted) {
|
|
1555
|
+
controller.abort(outerSignal.reason);
|
|
1556
|
+
return { controller, detach: () => void 0 };
|
|
1557
|
+
}
|
|
1558
|
+
outerSignal.addEventListener("abort", abortFromOuterSignal, { once: true });
|
|
1559
|
+
return {
|
|
1560
|
+
controller,
|
|
1561
|
+
detach: () => {
|
|
1562
|
+
outerSignal.removeEventListener("abort", abortFromOuterSignal);
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
createTailRuntime({
|
|
1567
|
+
parseEvent,
|
|
1568
|
+
replayCutoffSeq,
|
|
1569
|
+
startCursor,
|
|
1570
|
+
tailOptions
|
|
1571
|
+
}) {
|
|
1572
|
+
const queue = [];
|
|
1573
|
+
let notify;
|
|
1574
|
+
let done = false;
|
|
1575
|
+
let failure;
|
|
1576
|
+
const shouldApplyToLog = tailOptions.agent === void 0;
|
|
1577
|
+
const { controller, detach } = this.createTailAbortController(
|
|
1578
|
+
tailOptions.signal
|
|
1579
|
+
);
|
|
1580
|
+
const wake = () => {
|
|
1581
|
+
notify?.();
|
|
1582
|
+
};
|
|
1583
|
+
const streamTask = new TailStream({
|
|
1488
1584
|
sessionId: this.id,
|
|
1489
1585
|
token: this.token,
|
|
1490
1586
|
websocketBaseUrl: this.transport.websocketBaseUrl,
|
|
1491
1587
|
websocketFactory: this.transport.websocketFactory,
|
|
1492
1588
|
options: {
|
|
1493
1589
|
...tailOptions,
|
|
1494
|
-
cursor
|
|
1590
|
+
cursor: startCursor,
|
|
1591
|
+
signal: controller.signal
|
|
1592
|
+
}
|
|
1593
|
+
}).subscribe((batch) => {
|
|
1594
|
+
const queuedEvents = shouldApplyToLog ? this.log.applyBatch(batch) : batch;
|
|
1595
|
+
if (shouldApplyToLog && queuedEvents.length > 0) {
|
|
1596
|
+
this.persistLogState();
|
|
1495
1597
|
}
|
|
1598
|
+
for (const event of queuedEvents) {
|
|
1599
|
+
queue.push({
|
|
1600
|
+
event: parseEvent(event),
|
|
1601
|
+
context: this.resolveEventContext(event.seq, replayCutoffSeq)
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
wake();
|
|
1605
|
+
}).catch((error) => {
|
|
1606
|
+
failure = error;
|
|
1607
|
+
}).finally(() => {
|
|
1608
|
+
done = true;
|
|
1609
|
+
wake();
|
|
1496
1610
|
});
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
`consume() failed to save cursor for session '${this.id}': ${error instanceof Error ? error.message : String(error)}`
|
|
1505
|
-
);
|
|
1611
|
+
return {
|
|
1612
|
+
next: async () => {
|
|
1613
|
+
while (queue.length === 0 && !done && !failure) {
|
|
1614
|
+
await new Promise((resolve) => {
|
|
1615
|
+
notify = resolve;
|
|
1616
|
+
});
|
|
1617
|
+
notify = void 0;
|
|
1506
1618
|
}
|
|
1619
|
+
const next = queue.shift();
|
|
1620
|
+
return next;
|
|
1621
|
+
},
|
|
1622
|
+
getFailure: () => failure,
|
|
1623
|
+
dispose: async () => {
|
|
1624
|
+
controller.abort();
|
|
1625
|
+
detach();
|
|
1626
|
+
await streamTask;
|
|
1507
1627
|
}
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
async *iterateLiveTail({
|
|
1631
|
+
parseEvent,
|
|
1632
|
+
replayCutoffSeq,
|
|
1633
|
+
startCursor,
|
|
1634
|
+
tailOptions
|
|
1635
|
+
}) {
|
|
1636
|
+
const runtime = this.createTailRuntime({
|
|
1637
|
+
parseEvent,
|
|
1638
|
+
replayCutoffSeq,
|
|
1639
|
+
startCursor,
|
|
1640
|
+
tailOptions
|
|
1508
1641
|
});
|
|
1642
|
+
try {
|
|
1643
|
+
while (true) {
|
|
1644
|
+
const next = await runtime.next();
|
|
1645
|
+
if (next) {
|
|
1646
|
+
yield next;
|
|
1647
|
+
continue;
|
|
1648
|
+
}
|
|
1649
|
+
const failure = runtime.getFailure();
|
|
1650
|
+
if (failure) {
|
|
1651
|
+
throw failure;
|
|
1652
|
+
}
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
} finally {
|
|
1656
|
+
await runtime.dispose();
|
|
1657
|
+
}
|
|
1509
1658
|
}
|
|
1510
1659
|
emitStreamError(error) {
|
|
1511
1660
|
const streamError = error instanceof Error ? error : new StarciteError(`Session stream failed: ${String(error)}`);
|
|
@@ -1530,67 +1679,94 @@ var StarciteSession = class {
|
|
|
1530
1679
|
}).finally(() => {
|
|
1531
1680
|
this.liveSyncTask = void 0;
|
|
1532
1681
|
this.liveSyncController = void 0;
|
|
1682
|
+
if (this.eventSubscriptions.size > 0) {
|
|
1683
|
+
this.ensureLiveSync();
|
|
1684
|
+
}
|
|
1533
1685
|
});
|
|
1534
1686
|
}
|
|
1535
1687
|
async runLiveSync(signal) {
|
|
1688
|
+
let shouldRunCatchUpPass = this.log.lastSeq === 0;
|
|
1689
|
+
let retryDelayMs = 250;
|
|
1536
1690
|
while (!signal.aborted && this.eventSubscriptions.size > 0) {
|
|
1537
|
-
|
|
1538
|
-
sessionId: this.id,
|
|
1539
|
-
token: this.token,
|
|
1540
|
-
websocketBaseUrl: this.transport.websocketBaseUrl,
|
|
1541
|
-
websocketFactory: this.transport.websocketFactory,
|
|
1542
|
-
options: {
|
|
1543
|
-
cursor: this.log.lastSeq,
|
|
1544
|
-
signal
|
|
1545
|
-
}
|
|
1546
|
-
});
|
|
1691
|
+
this.liveSyncCatchUpActive = shouldRunCatchUpPass;
|
|
1547
1692
|
try {
|
|
1548
|
-
await
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
this.persistLogState();
|
|
1552
|
-
}
|
|
1553
|
-
});
|
|
1693
|
+
await this.subscribeLiveSyncPass(signal, !shouldRunCatchUpPass);
|
|
1694
|
+
shouldRunCatchUpPass = false;
|
|
1695
|
+
retryDelayMs = 250;
|
|
1554
1696
|
} catch (error) {
|
|
1555
1697
|
if (signal.aborted) {
|
|
1556
1698
|
return;
|
|
1557
1699
|
}
|
|
1558
1700
|
if (error instanceof SessionLogGapError) {
|
|
1701
|
+
shouldRunCatchUpPass = true;
|
|
1559
1702
|
continue;
|
|
1560
1703
|
}
|
|
1561
|
-
|
|
1704
|
+
this.emitStreamError(error);
|
|
1705
|
+
shouldRunCatchUpPass = true;
|
|
1706
|
+
await this.waitForLiveSyncRetry(retryDelayMs, signal);
|
|
1707
|
+
retryDelayMs = Math.min(retryDelayMs * 2, 5e3);
|
|
1708
|
+
} finally {
|
|
1709
|
+
this.liveSyncCatchUpActive = false;
|
|
1562
1710
|
}
|
|
1563
1711
|
}
|
|
1564
1712
|
}
|
|
1713
|
+
async subscribeLiveSyncPass(signal, follow) {
|
|
1714
|
+
const stream = new TailStream({
|
|
1715
|
+
sessionId: this.id,
|
|
1716
|
+
token: this.token,
|
|
1717
|
+
websocketBaseUrl: this.transport.websocketBaseUrl,
|
|
1718
|
+
websocketFactory: this.transport.websocketFactory,
|
|
1719
|
+
options: {
|
|
1720
|
+
cursor: this.log.lastSeq,
|
|
1721
|
+
follow,
|
|
1722
|
+
signal
|
|
1723
|
+
}
|
|
1724
|
+
});
|
|
1725
|
+
await stream.subscribe((batch) => {
|
|
1726
|
+
const appliedEvents = this.log.applyBatch(batch);
|
|
1727
|
+
if (appliedEvents.length > 0) {
|
|
1728
|
+
this.persistLogState();
|
|
1729
|
+
}
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1565
1732
|
persistLogState() {
|
|
1733
|
+
if (!this.store) {
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1566
1736
|
this.store.save(this.id, {
|
|
1567
1737
|
cursor: this.log.cursor,
|
|
1568
|
-
events: [...this.log.events]
|
|
1738
|
+
events: [...this.log.events],
|
|
1739
|
+
metadata: {
|
|
1740
|
+
schemaVersion: 1,
|
|
1741
|
+
updatedAtMs: Date.now()
|
|
1742
|
+
}
|
|
1569
1743
|
});
|
|
1570
1744
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1745
|
+
waitForLiveSyncRetry(delayMs, signal) {
|
|
1746
|
+
if (delayMs <= 0 || signal.aborted) {
|
|
1747
|
+
return Promise.resolve();
|
|
1748
|
+
}
|
|
1749
|
+
return new Promise((resolve) => {
|
|
1750
|
+
let settled = false;
|
|
1751
|
+
const timer = setTimeout(() => {
|
|
1752
|
+
if (settled) {
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
settled = true;
|
|
1756
|
+
signal.removeEventListener("abort", onAbort);
|
|
1757
|
+
resolve();
|
|
1758
|
+
}, delayMs);
|
|
1759
|
+
const onAbort = () => {
|
|
1760
|
+
if (settled) {
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
settled = true;
|
|
1764
|
+
clearTimeout(timer);
|
|
1765
|
+
signal.removeEventListener("abort", onAbort);
|
|
1766
|
+
resolve();
|
|
1767
|
+
};
|
|
1768
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1769
|
+
});
|
|
1594
1770
|
}
|
|
1595
1771
|
};
|
|
1596
1772
|
|
|
@@ -1630,7 +1806,7 @@ var Starcite = class {
|
|
|
1630
1806
|
}
|
|
1631
1807
|
this.authBaseUrl = resolveAuthBaseUrl(options.authUrl, apiKey);
|
|
1632
1808
|
const websocketFactory = options.websocketFactory ?? defaultWebSocketFactory;
|
|
1633
|
-
this.store = options.store
|
|
1809
|
+
this.store = options.store;
|
|
1634
1810
|
this.transport = {
|
|
1635
1811
|
baseUrl,
|
|
1636
1812
|
websocketBaseUrl: toWebSocketBaseUrl(baseUrl),
|
|
@@ -1697,7 +1873,20 @@ var Starcite = class {
|
|
|
1697
1873
|
async sessionFromIdentity(input) {
|
|
1698
1874
|
let sessionId = input.id;
|
|
1699
1875
|
let record;
|
|
1700
|
-
if (
|
|
1876
|
+
if (sessionId) {
|
|
1877
|
+
try {
|
|
1878
|
+
record = await this.createSession({
|
|
1879
|
+
id: sessionId,
|
|
1880
|
+
creator_principal: input.identity.toCreatorPrincipal(),
|
|
1881
|
+
title: input.title,
|
|
1882
|
+
metadata: input.metadata
|
|
1883
|
+
});
|
|
1884
|
+
} catch (error) {
|
|
1885
|
+
if (!(error instanceof StarciteApiError && error.status === 409)) {
|
|
1886
|
+
throw error;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
} else {
|
|
1701
1890
|
record = await this.createSession({
|
|
1702
1891
|
creator_principal: input.identity.toCreatorPrincipal(),
|
|
1703
1892
|
title: input.title,
|
|
@@ -1788,45 +1977,83 @@ var Starcite = class {
|
|
|
1788
1977
|
}
|
|
1789
1978
|
};
|
|
1790
1979
|
|
|
1791
|
-
// src/
|
|
1980
|
+
// src/session-store.ts
|
|
1981
|
+
var import_zod5 = require("zod");
|
|
1792
1982
|
var DEFAULT_KEY_PREFIX = "starcite";
|
|
1793
|
-
var
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1983
|
+
var SessionStoreMetadataSchema = import_zod5.z.object({
|
|
1984
|
+
schemaVersion: import_zod5.z.literal(1),
|
|
1985
|
+
updatedAtMs: import_zod5.z.number().int().nonnegative()
|
|
1986
|
+
});
|
|
1987
|
+
var SessionStoreStateSchema = import_zod5.z.object({
|
|
1988
|
+
cursor: import_zod5.z.number().int().nonnegative(),
|
|
1989
|
+
events: import_zod5.z.array(TailEventSchema),
|
|
1990
|
+
metadata: SessionStoreMetadataSchema.optional()
|
|
1991
|
+
});
|
|
1992
|
+
function cloneEvents(events) {
|
|
1993
|
+
return events.map((event) => structuredClone(event));
|
|
1994
|
+
}
|
|
1995
|
+
function cloneState(state) {
|
|
1996
|
+
return {
|
|
1997
|
+
cursor: state.cursor,
|
|
1998
|
+
events: cloneEvents(state.events),
|
|
1999
|
+
metadata: state.metadata ? { ...state.metadata } : void 0
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
var MemoryStore = class {
|
|
2003
|
+
sessions = /* @__PURE__ */ new Map();
|
|
1798
2004
|
load(sessionId) {
|
|
1799
|
-
|
|
2005
|
+
const stored = this.sessions.get(sessionId);
|
|
2006
|
+
return stored ? cloneState(stored) : void 0;
|
|
1800
2007
|
}
|
|
1801
|
-
save(sessionId,
|
|
1802
|
-
this.
|
|
2008
|
+
save(sessionId, state) {
|
|
2009
|
+
this.sessions.set(sessionId, cloneState(state));
|
|
2010
|
+
}
|
|
2011
|
+
clear(sessionId) {
|
|
2012
|
+
this.sessions.delete(sessionId);
|
|
1803
2013
|
}
|
|
1804
2014
|
};
|
|
1805
|
-
var
|
|
2015
|
+
var WebStorageSessionStore = class {
|
|
1806
2016
|
storage;
|
|
1807
2017
|
keyForSession;
|
|
2018
|
+
stateSchema;
|
|
1808
2019
|
constructor(storage, options = {}) {
|
|
1809
2020
|
this.storage = storage;
|
|
1810
2021
|
const prefix = options.keyPrefix?.trim() || DEFAULT_KEY_PREFIX;
|
|
1811
|
-
this.keyForSession = options.keyForSession ?? ((sessionId) => `${prefix}:${sessionId}:
|
|
2022
|
+
this.keyForSession = options.keyForSession ?? ((sessionId) => `${prefix}:${sessionId}:sessionStore`);
|
|
2023
|
+
this.stateSchema = options.stateSchema ?? SessionStoreStateSchema;
|
|
1812
2024
|
}
|
|
1813
2025
|
load(sessionId) {
|
|
1814
2026
|
const raw = this.storage.getItem(this.keyForSession(sessionId));
|
|
1815
2027
|
if (raw === null) {
|
|
1816
2028
|
return void 0;
|
|
1817
2029
|
}
|
|
1818
|
-
|
|
1819
|
-
|
|
2030
|
+
let decoded;
|
|
2031
|
+
try {
|
|
2032
|
+
decoded = JSON.parse(raw);
|
|
2033
|
+
} catch {
|
|
2034
|
+
return void 0;
|
|
2035
|
+
}
|
|
2036
|
+
const parsed = this.stateSchema.safeParse(decoded);
|
|
2037
|
+
if (!parsed.success) {
|
|
2038
|
+
return void 0;
|
|
2039
|
+
}
|
|
2040
|
+
return cloneState(parsed.data);
|
|
1820
2041
|
}
|
|
1821
|
-
save(sessionId,
|
|
1822
|
-
this.storage.setItem(
|
|
2042
|
+
save(sessionId, state) {
|
|
2043
|
+
this.storage.setItem(
|
|
2044
|
+
this.keyForSession(sessionId),
|
|
2045
|
+
JSON.stringify(cloneState(state))
|
|
2046
|
+
);
|
|
2047
|
+
}
|
|
2048
|
+
clear(sessionId) {
|
|
2049
|
+
this.storage.removeItem?.(this.keyForSession(sessionId));
|
|
1823
2050
|
}
|
|
1824
2051
|
};
|
|
1825
|
-
var
|
|
2052
|
+
var LocalStorageSessionStore = class extends WebStorageSessionStore {
|
|
1826
2053
|
constructor(options = {}) {
|
|
1827
2054
|
if (typeof localStorage === "undefined") {
|
|
1828
2055
|
throw new StarciteError(
|
|
1829
|
-
"localStorage is not available in this runtime. Use
|
|
2056
|
+
"localStorage is not available in this runtime. Use WebStorageSessionStore with a custom storage adapter."
|
|
1830
2057
|
);
|
|
1831
2058
|
}
|
|
1832
2059
|
super(localStorage, options);
|
|
@@ -1834,8 +2061,7 @@ var LocalStorageCursorStore = class extends WebStorageCursorStore {
|
|
|
1834
2061
|
};
|
|
1835
2062
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1836
2063
|
0 && (module.exports = {
|
|
1837
|
-
|
|
1838
|
-
LocalStorageCursorStore,
|
|
2064
|
+
LocalStorageSessionStore,
|
|
1839
2065
|
MemoryStore,
|
|
1840
2066
|
SessionLogConflictError,
|
|
1841
2067
|
SessionLogGapError,
|
|
@@ -1849,6 +2075,6 @@ var LocalStorageCursorStore = class extends WebStorageCursorStore {
|
|
|
1849
2075
|
StarciteSession,
|
|
1850
2076
|
StarciteTailError,
|
|
1851
2077
|
StarciteTokenExpiredError,
|
|
1852
|
-
|
|
2078
|
+
WebStorageSessionStore
|
|
1853
2079
|
});
|
|
1854
2080
|
//# sourceMappingURL=index.cjs.map
|