@lessonkit/core 1.3.0 → 1.3.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 +41 -11
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +41 -11
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -620,12 +620,14 @@ function createTrackingClient(opts) {
|
|
|
620
620
|
}
|
|
621
621
|
const buffer = [];
|
|
622
622
|
let flushInFlight = null;
|
|
623
|
+
let inflightExitBatch = null;
|
|
623
624
|
let disposed = false;
|
|
624
625
|
let disposing = false;
|
|
625
626
|
let intervalId;
|
|
626
627
|
const runFlush = () => {
|
|
627
628
|
if (!buffer.length) return Promise.resolve(true);
|
|
628
629
|
const events = buffer.splice(0, buffer.length);
|
|
630
|
+
inflightExitBatch = events;
|
|
629
631
|
let succeeded = false;
|
|
630
632
|
return Promise.resolve().then(async () => {
|
|
631
633
|
if (batchSink) {
|
|
@@ -650,6 +652,8 @@ function createTrackingClient(opts) {
|
|
|
650
652
|
return runFlush();
|
|
651
653
|
}
|
|
652
654
|
return succeeded;
|
|
655
|
+
}).finally(() => {
|
|
656
|
+
inflightExitBatch = null;
|
|
653
657
|
});
|
|
654
658
|
};
|
|
655
659
|
const flush = () => {
|
|
@@ -697,6 +701,22 @@ function createTrackingClient(opts) {
|
|
|
697
701
|
if (buffer.length >= maxBatchSize) void flush();
|
|
698
702
|
},
|
|
699
703
|
flush,
|
|
704
|
+
flushOnExit: opts?.exitBatchSink ? () => {
|
|
705
|
+
const fromBuffer = buffer.splice(0, buffer.length);
|
|
706
|
+
const fromInflight = inflightExitBatch ? [...inflightExitBatch] : [];
|
|
707
|
+
const events = [...fromInflight, ...fromBuffer];
|
|
708
|
+
if (!events.length) return;
|
|
709
|
+
try {
|
|
710
|
+
const result = opts.exitBatchSink(events);
|
|
711
|
+
if (result != null && typeof result.catch === "function") {
|
|
712
|
+
void result.catch(() => {
|
|
713
|
+
buffer.unshift(...events);
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
} catch {
|
|
717
|
+
buffer.unshift(...events);
|
|
718
|
+
}
|
|
719
|
+
} : void 0,
|
|
700
720
|
dispose: () => {
|
|
701
721
|
if (disposed || disposing) return Promise.resolve();
|
|
702
722
|
disposing = true;
|
|
@@ -1207,16 +1227,16 @@ function hasCourseStartedEmittedToTracking(storage, sessionId, courseId) {
|
|
|
1207
1227
|
return storage.getItem(courseStartedTrackingStorageKey(sessionId, courseId)) === "1";
|
|
1208
1228
|
}
|
|
1209
1229
|
function markCourseStartedEmittedToTracking(storage, sessionId, courseId) {
|
|
1210
|
-
if (!courseId) return;
|
|
1211
|
-
storage.setItem(courseStartedTrackingStorageKey(sessionId, courseId), "1");
|
|
1230
|
+
if (!courseId) return false;
|
|
1231
|
+
return storage.setItem(courseStartedTrackingStorageKey(sessionId, courseId), "1");
|
|
1212
1232
|
}
|
|
1213
1233
|
function hasCourseStartedPipelineDelivered(storage, sessionId, courseId) {
|
|
1214
1234
|
if (!courseId) return false;
|
|
1215
1235
|
return storage.getItem(courseStartedPipelineStorageKey(sessionId, courseId)) === "1";
|
|
1216
1236
|
}
|
|
1217
1237
|
function markCourseStartedPipelineDelivered(storage, sessionId, courseId) {
|
|
1218
|
-
if (!courseId) return;
|
|
1219
|
-
storage.setItem(courseStartedPipelineStorageKey(sessionId, courseId), "1");
|
|
1238
|
+
if (!courseId) return false;
|
|
1239
|
+
return storage.setItem(courseStartedPipelineStorageKey(sessionId, courseId), "1");
|
|
1220
1240
|
}
|
|
1221
1241
|
function resetSharedVolatileSessionIdForTests() {
|
|
1222
1242
|
sharedVolatileSessionId = null;
|
|
@@ -1238,19 +1258,29 @@ function migrateCourseStartedMark(storage, fromSessionId, toSessionId, courseId)
|
|
|
1238
1258
|
}
|
|
1239
1259
|
|
|
1240
1260
|
// src/runtime/courseLifecycle.ts
|
|
1261
|
+
var courseStartedEmitFlights = /* @__PURE__ */ new Set();
|
|
1241
1262
|
function tryEmitCourseStarted(ctx, deps, alreadyEmittedToSink) {
|
|
1263
|
+
const flightKey = `${ctx.sessionId}:${ctx.courseId}`;
|
|
1242
1264
|
const marked = hasCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId);
|
|
1243
1265
|
if (alreadyEmittedToSink) {
|
|
1244
1266
|
return { emitted: true, marked };
|
|
1245
1267
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1268
|
+
if (courseStartedEmitFlights.has(flightKey)) {
|
|
1269
|
+
return { emitted: false, marked };
|
|
1270
|
+
}
|
|
1271
|
+
courseStartedEmitFlights.add(flightKey);
|
|
1272
|
+
try {
|
|
1273
|
+
const emitted = deps.emitCourseStartedEvent(ctx);
|
|
1274
|
+
if (emitted && !marked) {
|
|
1275
|
+
markCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId);
|
|
1276
|
+
}
|
|
1277
|
+
return {
|
|
1278
|
+
emitted,
|
|
1279
|
+
marked: hasCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId)
|
|
1280
|
+
};
|
|
1281
|
+
} finally {
|
|
1282
|
+
courseStartedEmitFlights.delete(flightKey);
|
|
1249
1283
|
}
|
|
1250
|
-
return {
|
|
1251
|
-
emitted,
|
|
1252
|
-
marked: hasCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId)
|
|
1253
|
-
};
|
|
1254
1284
|
}
|
|
1255
1285
|
function buildCourseStartedTelemetryEvent(ctx) {
|
|
1256
1286
|
return buildTelemetryEvent({
|
package/dist/index.d.cts
CHANGED
|
@@ -259,6 +259,8 @@ type TrackingClient = {
|
|
|
259
259
|
track: (event: TelemetryEvent) => void;
|
|
260
260
|
/** Resolves to true when all buffered events were delivered; false when a sink failure re-queued events. */
|
|
261
261
|
flush?: () => void | Promise<boolean>;
|
|
262
|
+
/** Best-effort synchronous flush for pagehide (keepalive batch sink when configured). */
|
|
263
|
+
flushOnExit?: () => void;
|
|
262
264
|
dispose?: () => void | Promise<void>;
|
|
263
265
|
};
|
|
264
266
|
|
|
@@ -385,6 +387,8 @@ declare function createTrackingClient(opts?: {
|
|
|
385
387
|
batchSink?: TelemetryBatchSink;
|
|
386
388
|
/** Called when an event is dropped because the batch buffer is at cap (including in production). */
|
|
387
389
|
onBufferDrop?: () => void;
|
|
390
|
+
/** Keepalive batch delivery for pagehide (e.g. from createFetchBatchSink). */
|
|
391
|
+
exitBatchSink?: TelemetryBatchSink;
|
|
388
392
|
}): TrackingClient;
|
|
389
393
|
|
|
390
394
|
declare function createSessionId(): string;
|
|
@@ -523,9 +527,9 @@ declare function resolveSessionId(storage: StoragePort, provided?: string): stri
|
|
|
523
527
|
declare function hasCourseStarted(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
524
528
|
declare function markCourseStarted(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
525
529
|
declare function hasCourseStartedEmittedToTracking(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
526
|
-
declare function markCourseStartedEmittedToTracking(storage: StoragePort, sessionId: string, courseId?: CourseId):
|
|
530
|
+
declare function markCourseStartedEmittedToTracking(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
527
531
|
declare function hasCourseStartedPipelineDelivered(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
528
|
-
declare function markCourseStartedPipelineDelivered(storage: StoragePort, sessionId: string, courseId?: CourseId):
|
|
532
|
+
declare function markCourseStartedPipelineDelivered(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
529
533
|
/** @internal Reset shared volatile session id between tests. */
|
|
530
534
|
declare function resetSharedVolatileSessionIdForTests(): void;
|
|
531
535
|
declare function migrateCourseStartedMark(storage: StoragePort, fromSessionId: string, toSessionId: string, courseId?: CourseId): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -259,6 +259,8 @@ type TrackingClient = {
|
|
|
259
259
|
track: (event: TelemetryEvent) => void;
|
|
260
260
|
/** Resolves to true when all buffered events were delivered; false when a sink failure re-queued events. */
|
|
261
261
|
flush?: () => void | Promise<boolean>;
|
|
262
|
+
/** Best-effort synchronous flush for pagehide (keepalive batch sink when configured). */
|
|
263
|
+
flushOnExit?: () => void;
|
|
262
264
|
dispose?: () => void | Promise<void>;
|
|
263
265
|
};
|
|
264
266
|
|
|
@@ -385,6 +387,8 @@ declare function createTrackingClient(opts?: {
|
|
|
385
387
|
batchSink?: TelemetryBatchSink;
|
|
386
388
|
/** Called when an event is dropped because the batch buffer is at cap (including in production). */
|
|
387
389
|
onBufferDrop?: () => void;
|
|
390
|
+
/** Keepalive batch delivery for pagehide (e.g. from createFetchBatchSink). */
|
|
391
|
+
exitBatchSink?: TelemetryBatchSink;
|
|
388
392
|
}): TrackingClient;
|
|
389
393
|
|
|
390
394
|
declare function createSessionId(): string;
|
|
@@ -523,9 +527,9 @@ declare function resolveSessionId(storage: StoragePort, provided?: string): stri
|
|
|
523
527
|
declare function hasCourseStarted(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
524
528
|
declare function markCourseStarted(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
525
529
|
declare function hasCourseStartedEmittedToTracking(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
526
|
-
declare function markCourseStartedEmittedToTracking(storage: StoragePort, sessionId: string, courseId?: CourseId):
|
|
530
|
+
declare function markCourseStartedEmittedToTracking(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
527
531
|
declare function hasCourseStartedPipelineDelivered(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
528
|
-
declare function markCourseStartedPipelineDelivered(storage: StoragePort, sessionId: string, courseId?: CourseId):
|
|
532
|
+
declare function markCourseStartedPipelineDelivered(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
529
533
|
/** @internal Reset shared volatile session id between tests. */
|
|
530
534
|
declare function resetSharedVolatileSessionIdForTests(): void;
|
|
531
535
|
declare function migrateCourseStartedMark(storage: StoragePort, fromSessionId: string, toSessionId: string, courseId?: CourseId): void;
|
package/dist/index.js
CHANGED
|
@@ -522,12 +522,14 @@ function createTrackingClient(opts) {
|
|
|
522
522
|
}
|
|
523
523
|
const buffer = [];
|
|
524
524
|
let flushInFlight = null;
|
|
525
|
+
let inflightExitBatch = null;
|
|
525
526
|
let disposed = false;
|
|
526
527
|
let disposing = false;
|
|
527
528
|
let intervalId;
|
|
528
529
|
const runFlush = () => {
|
|
529
530
|
if (!buffer.length) return Promise.resolve(true);
|
|
530
531
|
const events = buffer.splice(0, buffer.length);
|
|
532
|
+
inflightExitBatch = events;
|
|
531
533
|
let succeeded = false;
|
|
532
534
|
return Promise.resolve().then(async () => {
|
|
533
535
|
if (batchSink) {
|
|
@@ -552,6 +554,8 @@ function createTrackingClient(opts) {
|
|
|
552
554
|
return runFlush();
|
|
553
555
|
}
|
|
554
556
|
return succeeded;
|
|
557
|
+
}).finally(() => {
|
|
558
|
+
inflightExitBatch = null;
|
|
555
559
|
});
|
|
556
560
|
};
|
|
557
561
|
const flush = () => {
|
|
@@ -599,6 +603,22 @@ function createTrackingClient(opts) {
|
|
|
599
603
|
if (buffer.length >= maxBatchSize) void flush();
|
|
600
604
|
},
|
|
601
605
|
flush,
|
|
606
|
+
flushOnExit: opts?.exitBatchSink ? () => {
|
|
607
|
+
const fromBuffer = buffer.splice(0, buffer.length);
|
|
608
|
+
const fromInflight = inflightExitBatch ? [...inflightExitBatch] : [];
|
|
609
|
+
const events = [...fromInflight, ...fromBuffer];
|
|
610
|
+
if (!events.length) return;
|
|
611
|
+
try {
|
|
612
|
+
const result = opts.exitBatchSink(events);
|
|
613
|
+
if (result != null && typeof result.catch === "function") {
|
|
614
|
+
void result.catch(() => {
|
|
615
|
+
buffer.unshift(...events);
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
} catch {
|
|
619
|
+
buffer.unshift(...events);
|
|
620
|
+
}
|
|
621
|
+
} : void 0,
|
|
602
622
|
dispose: () => {
|
|
603
623
|
if (disposed || disposing) return Promise.resolve();
|
|
604
624
|
disposing = true;
|
|
@@ -1109,16 +1129,16 @@ function hasCourseStartedEmittedToTracking(storage, sessionId, courseId) {
|
|
|
1109
1129
|
return storage.getItem(courseStartedTrackingStorageKey(sessionId, courseId)) === "1";
|
|
1110
1130
|
}
|
|
1111
1131
|
function markCourseStartedEmittedToTracking(storage, sessionId, courseId) {
|
|
1112
|
-
if (!courseId) return;
|
|
1113
|
-
storage.setItem(courseStartedTrackingStorageKey(sessionId, courseId), "1");
|
|
1132
|
+
if (!courseId) return false;
|
|
1133
|
+
return storage.setItem(courseStartedTrackingStorageKey(sessionId, courseId), "1");
|
|
1114
1134
|
}
|
|
1115
1135
|
function hasCourseStartedPipelineDelivered(storage, sessionId, courseId) {
|
|
1116
1136
|
if (!courseId) return false;
|
|
1117
1137
|
return storage.getItem(courseStartedPipelineStorageKey(sessionId, courseId)) === "1";
|
|
1118
1138
|
}
|
|
1119
1139
|
function markCourseStartedPipelineDelivered(storage, sessionId, courseId) {
|
|
1120
|
-
if (!courseId) return;
|
|
1121
|
-
storage.setItem(courseStartedPipelineStorageKey(sessionId, courseId), "1");
|
|
1140
|
+
if (!courseId) return false;
|
|
1141
|
+
return storage.setItem(courseStartedPipelineStorageKey(sessionId, courseId), "1");
|
|
1122
1142
|
}
|
|
1123
1143
|
function resetSharedVolatileSessionIdForTests() {
|
|
1124
1144
|
sharedVolatileSessionId = null;
|
|
@@ -1140,19 +1160,29 @@ function migrateCourseStartedMark(storage, fromSessionId, toSessionId, courseId)
|
|
|
1140
1160
|
}
|
|
1141
1161
|
|
|
1142
1162
|
// src/runtime/courseLifecycle.ts
|
|
1163
|
+
var courseStartedEmitFlights = /* @__PURE__ */ new Set();
|
|
1143
1164
|
function tryEmitCourseStarted(ctx, deps, alreadyEmittedToSink) {
|
|
1165
|
+
const flightKey = `${ctx.sessionId}:${ctx.courseId}`;
|
|
1144
1166
|
const marked = hasCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId);
|
|
1145
1167
|
if (alreadyEmittedToSink) {
|
|
1146
1168
|
return { emitted: true, marked };
|
|
1147
1169
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1170
|
+
if (courseStartedEmitFlights.has(flightKey)) {
|
|
1171
|
+
return { emitted: false, marked };
|
|
1172
|
+
}
|
|
1173
|
+
courseStartedEmitFlights.add(flightKey);
|
|
1174
|
+
try {
|
|
1175
|
+
const emitted = deps.emitCourseStartedEvent(ctx);
|
|
1176
|
+
if (emitted && !marked) {
|
|
1177
|
+
markCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId);
|
|
1178
|
+
}
|
|
1179
|
+
return {
|
|
1180
|
+
emitted,
|
|
1181
|
+
marked: hasCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId)
|
|
1182
|
+
};
|
|
1183
|
+
} finally {
|
|
1184
|
+
courseStartedEmitFlights.delete(flightKey);
|
|
1151
1185
|
}
|
|
1152
|
-
return {
|
|
1153
|
-
emitted,
|
|
1154
|
-
marked: hasCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId)
|
|
1155
|
-
};
|
|
1156
1186
|
}
|
|
1157
1187
|
function buildCourseStartedTelemetryEvent(ctx) {
|
|
1158
1188
|
return buildTelemetryEvent({
|