@lessonkit/xapi 1.0.0 → 1.0.2

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 CHANGED
@@ -59,10 +59,11 @@ function createInMemoryXAPIQueue() {
59
59
  }
60
60
 
61
61
  // src/client.ts
62
- var import_core2 = require("@lessonkit/core");
62
+ var import_core3 = require("@lessonkit/core");
63
63
 
64
64
  // src/telemetryMap.ts
65
65
  var import_core = require("@lessonkit/core");
66
+ var import_core2 = require("@lessonkit/core");
66
67
 
67
68
  // src/id.ts
68
69
  function cryptoRandomId() {
@@ -90,13 +91,13 @@ function telemetryEventToXAPIStatement(event) {
90
91
  const { courseId } = event;
91
92
  switch (event.name) {
92
93
  case "course_started":
93
- return statementFor((0, import_core.buildLessonkitUrn)({ courseId }), XAPIVerbs.initialized, event.timestamp);
94
+ return statementFor((0, import_core2.buildLessonkitUrn)({ courseId }), XAPIVerbs.initialized, event.timestamp);
94
95
  case "course_completed":
95
- return statementFor((0, import_core.buildLessonkitUrn)({ courseId }), XAPIVerbs.completed, event.timestamp);
96
+ return statementFor((0, import_core2.buildLessonkitUrn)({ courseId }), XAPIVerbs.completed, event.timestamp);
96
97
  case "lesson_started": {
97
98
  const lessonId = event.lessonId;
98
99
  return statementFor(
99
- (0, import_core.buildLessonkitUrn)({ courseId, lessonId }),
100
+ (0, import_core2.buildLessonkitUrn)({ courseId, lessonId }),
100
101
  XAPIVerbs.initialized,
101
102
  event.timestamp
102
103
  );
@@ -119,7 +120,7 @@ function telemetryEventToXAPIStatement(event) {
119
120
  scaled: typeof raw === "number" && typeof max === "number" && max > 0 ? raw / max : void 0
120
121
  };
121
122
  }
122
- return statementFor((0, import_core.buildLessonkitUrn)({ courseId, lessonId }), XAPIVerbs.completed, event.timestamp, {
123
+ return statementFor((0, import_core2.buildLessonkitUrn)({ courseId, lessonId }), XAPIVerbs.completed, event.timestamp, {
123
124
  result: Object.keys(result).length ? result : void 0
124
125
  });
125
126
  }
@@ -133,7 +134,7 @@ function telemetryEventToXAPIStatement(event) {
133
134
  result.success = event.data.correct;
134
135
  }
135
136
  return statementFor(
136
- (0, import_core.buildLessonkitUrn)({ courseId, lessonId, checkId }),
137
+ (0, import_core2.buildLessonkitUrn)({ courseId, lessonId, checkId }),
137
138
  XAPIVerbs.answered,
138
139
  event.timestamp,
139
140
  { result: Object.keys(result).length ? result : void 0 }
@@ -155,7 +156,7 @@ function telemetryEventToXAPIStatement(event) {
155
156
  };
156
157
  }
157
158
  return statementFor(
158
- (0, import_core.buildLessonkitUrn)({ courseId, lessonId, checkId }),
159
+ (0, import_core2.buildLessonkitUrn)({ courseId, lessonId, checkId }),
159
160
  XAPIVerbs.completed,
160
161
  event.timestamp,
161
162
  { result: Object.keys(result).length ? result : void 0 }
@@ -166,13 +167,13 @@ function telemetryEventToXAPIStatement(event) {
166
167
  const blockId = event.data?.blockId;
167
168
  if (!lessonId || !blockId) return null;
168
169
  return statementFor(
169
- (0, import_core.buildLessonkitUrn)({ courseId, lessonId, blockId }),
170
+ (0, import_core2.buildLessonkitUrn)({ courseId, lessonId, blockId }),
170
171
  XAPIVerbs.experienced,
171
172
  event.timestamp
172
173
  );
173
174
  }
174
175
  default:
175
- return null;
176
+ return (0, import_core.assertNever)(event, "Unhandled telemetry event");
176
177
  }
177
178
  }
178
179
  function statementFor(objectId, verb, timestamp, extra) {
@@ -210,8 +211,17 @@ function createXAPIClient(opts) {
210
211
  return;
211
212
  }
212
213
  const existing = inflightById.get(statement.id);
213
- if (existing) return;
214
- const flight = Promise.resolve().then(() => transport(statement)).catch(() => {
214
+ if (existing) {
215
+ void existing.then(
216
+ () => void 0,
217
+ () => {
218
+ sendOrQueue(statement);
219
+ }
220
+ );
221
+ return;
222
+ }
223
+ const transportFlight = Promise.resolve().then(() => transport(statement));
224
+ const flight = transportFlight.catch(() => {
215
225
  queue.enqueue(statement);
216
226
  if (isDevEnvironment() && !warnedTransportFailure) {
217
227
  warnedTransportFailure = true;
@@ -222,7 +232,7 @@ function createXAPIClient(opts) {
222
232
  }).finally(() => {
223
233
  inflightById.delete(statement.id);
224
234
  });
225
- inflightById.set(statement.id, flight);
235
+ inflightById.set(statement.id, transportFlight);
226
236
  void flight;
227
237
  };
228
238
  const emit = (event) => {
@@ -237,12 +247,16 @@ function createXAPIClient(opts) {
237
247
  flush: async () => {
238
248
  if (!transport) return;
239
249
  await queue.flush(transport);
250
+ const flights = [...inflightById.values()];
251
+ if (flights.length > 0) {
252
+ await Promise.allSettled(flights);
253
+ }
240
254
  },
241
255
  startedLesson: ({ lessonId }) => {
242
256
  if (!courseId) return;
243
257
  emit({
244
258
  name: "lesson_started",
245
- timestamp: (0, import_core2.nowIso)(),
259
+ timestamp: (0, import_core3.nowIso)(),
246
260
  courseId,
247
261
  lessonId,
248
262
  data: { lessonId }
@@ -258,7 +272,7 @@ function createXAPIClient(opts) {
258
272
  if (!courseId) return;
259
273
  emit({
260
274
  name: "lesson_completed",
261
- timestamp: (0, import_core2.nowIso)(),
275
+ timestamp: (0, import_core3.nowIso)(),
262
276
  courseId,
263
277
  lessonId,
264
278
  data: { lessonId, durationMs, score, maxScore, success }
@@ -268,7 +282,7 @@ function createXAPIClient(opts) {
268
282
  if (!courseId) return;
269
283
  emit({
270
284
  name: "course_completed",
271
- timestamp: (0, import_core2.nowIso)(),
285
+ timestamp: (0, import_core3.nowIso)(),
272
286
  courseId
273
287
  });
274
288
  }
package/dist/index.d.cts CHANGED
@@ -1,14 +1,32 @@
1
1
  import { LessonId, CourseId, TelemetryEvent } from '@lessonkit/core';
2
2
 
3
+ type XAPIVerbIri = "http://adlnet.gov/expapi/verbs/initialized" | "http://adlnet.gov/expapi/verbs/completed" | "http://adlnet.gov/expapi/verbs/answered" | "http://adlnet.gov/expapi/verbs/experienced";
4
+ type XAPIScore = {
5
+ raw?: number;
6
+ max?: number;
7
+ min?: number;
8
+ scaled?: number;
9
+ };
10
+ type XAPIResult = {
11
+ duration?: string;
12
+ success?: boolean;
13
+ score?: XAPIScore;
14
+ completion?: boolean;
15
+ };
16
+ type XAPIObjectDefinition = {
17
+ name?: Record<string, string>;
18
+ description?: Record<string, string>;
19
+ type?: string;
20
+ };
3
21
  type XAPIStatement = {
4
22
  id: string;
5
23
  timestamp: string;
6
- verb: string;
24
+ verb: XAPIVerbIri;
7
25
  object: {
8
26
  id: string;
9
- definition?: Record<string, unknown>;
27
+ definition?: XAPIObjectDefinition;
10
28
  };
11
- result?: Record<string, unknown>;
29
+ result?: XAPIResult;
12
30
  context?: Record<string, unknown>;
13
31
  };
14
32
  type XAPITransport = (statement: XAPIStatement) => void | Promise<void>;
@@ -48,4 +66,4 @@ declare function createXAPIClient(opts?: {
48
66
  */
49
67
  declare function telemetryEventToXAPIStatement(event: TelemetryEvent): XAPIStatement | null;
50
68
 
51
- export { type XAPIClient, type XAPIQueue, type XAPIStatement, type XAPITransport, createInMemoryXAPIQueue, createXAPIClient, telemetryEventToXAPIStatement };
69
+ export { type XAPIClient, type XAPIObjectDefinition, type XAPIQueue, type XAPIResult, type XAPIScore, type XAPIStatement, type XAPITransport, type XAPIVerbIri, createInMemoryXAPIQueue, createXAPIClient, telemetryEventToXAPIStatement };
package/dist/index.d.ts CHANGED
@@ -1,14 +1,32 @@
1
1
  import { LessonId, CourseId, TelemetryEvent } from '@lessonkit/core';
2
2
 
3
+ type XAPIVerbIri = "http://adlnet.gov/expapi/verbs/initialized" | "http://adlnet.gov/expapi/verbs/completed" | "http://adlnet.gov/expapi/verbs/answered" | "http://adlnet.gov/expapi/verbs/experienced";
4
+ type XAPIScore = {
5
+ raw?: number;
6
+ max?: number;
7
+ min?: number;
8
+ scaled?: number;
9
+ };
10
+ type XAPIResult = {
11
+ duration?: string;
12
+ success?: boolean;
13
+ score?: XAPIScore;
14
+ completion?: boolean;
15
+ };
16
+ type XAPIObjectDefinition = {
17
+ name?: Record<string, string>;
18
+ description?: Record<string, string>;
19
+ type?: string;
20
+ };
3
21
  type XAPIStatement = {
4
22
  id: string;
5
23
  timestamp: string;
6
- verb: string;
24
+ verb: XAPIVerbIri;
7
25
  object: {
8
26
  id: string;
9
- definition?: Record<string, unknown>;
27
+ definition?: XAPIObjectDefinition;
10
28
  };
11
- result?: Record<string, unknown>;
29
+ result?: XAPIResult;
12
30
  context?: Record<string, unknown>;
13
31
  };
14
32
  type XAPITransport = (statement: XAPIStatement) => void | Promise<void>;
@@ -48,4 +66,4 @@ declare function createXAPIClient(opts?: {
48
66
  */
49
67
  declare function telemetryEventToXAPIStatement(event: TelemetryEvent): XAPIStatement | null;
50
68
 
51
- export { type XAPIClient, type XAPIQueue, type XAPIStatement, type XAPITransport, createInMemoryXAPIQueue, createXAPIClient, telemetryEventToXAPIStatement };
69
+ export { type XAPIClient, type XAPIObjectDefinition, type XAPIQueue, type XAPIResult, type XAPIScore, type XAPIStatement, type XAPITransport, type XAPIVerbIri, createInMemoryXAPIQueue, createXAPIClient, telemetryEventToXAPIStatement };
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ function createInMemoryXAPIQueue() {
34
34
  import { nowIso } from "@lessonkit/core";
35
35
 
36
36
  // src/telemetryMap.ts
37
+ import { assertNever } from "@lessonkit/core";
37
38
  import { buildLessonkitUrn } from "@lessonkit/core";
38
39
 
39
40
  // src/id.ts
@@ -144,7 +145,7 @@ function telemetryEventToXAPIStatement(event) {
144
145
  );
145
146
  }
146
147
  default:
147
- return null;
148
+ return assertNever(event, "Unhandled telemetry event");
148
149
  }
149
150
  }
150
151
  function statementFor(objectId, verb, timestamp, extra) {
@@ -182,8 +183,17 @@ function createXAPIClient(opts) {
182
183
  return;
183
184
  }
184
185
  const existing = inflightById.get(statement.id);
185
- if (existing) return;
186
- const flight = Promise.resolve().then(() => transport(statement)).catch(() => {
186
+ if (existing) {
187
+ void existing.then(
188
+ () => void 0,
189
+ () => {
190
+ sendOrQueue(statement);
191
+ }
192
+ );
193
+ return;
194
+ }
195
+ const transportFlight = Promise.resolve().then(() => transport(statement));
196
+ const flight = transportFlight.catch(() => {
187
197
  queue.enqueue(statement);
188
198
  if (isDevEnvironment() && !warnedTransportFailure) {
189
199
  warnedTransportFailure = true;
@@ -194,7 +204,7 @@ function createXAPIClient(opts) {
194
204
  }).finally(() => {
195
205
  inflightById.delete(statement.id);
196
206
  });
197
- inflightById.set(statement.id, flight);
207
+ inflightById.set(statement.id, transportFlight);
198
208
  void flight;
199
209
  };
200
210
  const emit = (event) => {
@@ -209,6 +219,10 @@ function createXAPIClient(opts) {
209
219
  flush: async () => {
210
220
  if (!transport) return;
211
221
  await queue.flush(transport);
222
+ const flights = [...inflightById.values()];
223
+ if (flights.length > 0) {
224
+ await Promise.allSettled(flights);
225
+ }
212
226
  },
213
227
  startedLesson: ({ lessonId }) => {
214
228
  if (!courseId) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessonkit/xapi",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "private": false,
5
5
  "description": "xAPI statement generation primitives for LessonKit.",
6
6
  "license": "Apache-2.0",
@@ -48,11 +48,11 @@
48
48
  "lint": "echo \"(no lint configured yet)\""
49
49
  },
50
50
  "dependencies": {
51
- "@lessonkit/core": "1.0.0"
51
+ "@lessonkit/core": "1.0.2"
52
52
  },
53
53
  "devDependencies": {
54
54
  "tsup": "^8.5.0",
55
55
  "typescript": "^5.8.3",
56
- "vitest": "^3.2.4"
56
+ "vitest": "^4.1.8"
57
57
  }
58
58
  }