@lessonkit/xapi 0.9.3 → 1.0.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/README.md +25 -19
- package/dist/index.cjs +50 -23
- package/dist/index.d.cts +22 -4
- package/dist/index.d.ts +22 -4
- package/dist/index.js +39 -12
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,41 +1,47 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @lessonkit/xapi
|
|
2
2
|
|
|
3
|
-
[](https://github.com/eddiethedean/lessonkit/actions/workflows/ci.yml)
|
|
4
|
-
[](https://lessonkit.readthedocs.io/en/latest/)
|
|
5
3
|
[](https://www.npmjs.com/package/@lessonkit/xapi)
|
|
4
|
+
[](https://lessonkit.readthedocs.io/en/latest/reference/xapi.html)
|
|
6
5
|
[](https://github.com/eddiethedean/lessonkit/blob/main/LICENSE)
|
|
7
6
|
|
|
8
|
-
xAPI statement generation
|
|
7
|
+
xAPI statement generation, in-memory queueing, and telemetry-to-xAPI mapping.
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
Requires Node.js **18+**.
|
|
11
10
|
|
|
12
11
|
## Install
|
|
13
12
|
|
|
14
13
|
```bash
|
|
15
|
-
npm install @lessonkit/xapi
|
|
14
|
+
npm install @lessonkit/xapi @lessonkit/core
|
|
16
15
|
```
|
|
17
16
|
|
|
18
|
-
##
|
|
17
|
+
## Usage
|
|
19
18
|
|
|
20
|
-
```
|
|
21
|
-
import { createXAPIClient } from "@lessonkit/xapi";
|
|
19
|
+
```typescript
|
|
20
|
+
import { createXAPIClient, telemetryEventToXAPIStatement } from "@lessonkit/xapi";
|
|
22
21
|
|
|
23
22
|
const xapi = createXAPIClient({
|
|
24
|
-
courseId: "
|
|
25
|
-
transport: (statement) => {
|
|
26
|
-
|
|
23
|
+
courseId: "my-course",
|
|
24
|
+
transport: async (statement) => {
|
|
25
|
+
await fetch("/xapi/statements", { method: "POST", body: JSON.stringify(statement) });
|
|
27
26
|
},
|
|
28
27
|
});
|
|
29
28
|
|
|
30
|
-
xapi.completeLesson({ lessonId: "
|
|
29
|
+
xapi.completeLesson({ lessonId: "lesson-1", durationMs: 1200, success: true });
|
|
30
|
+
await xapi.flush();
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
Map from telemetry events: `telemetryEventToXAPIStatement(event)` — uses canonical LessonKit URNs.
|
|
34
34
|
|
|
35
|
-
##
|
|
35
|
+
## Behavior
|
|
36
36
|
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
37
|
+
- No transport → statements queue in memory (dev warns once).
|
|
38
|
+
- Transport failure → re-queue; call `flush()` to retry.
|
|
39
|
+
- Concurrent `flush()` calls are coalesced.
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
## Docs
|
|
42
|
+
|
|
43
|
+
[xAPI reference](https://lessonkit.readthedocs.io/en/latest/reference/xapi.html) · [Telemetry & xAPI guide](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/telemetry-and-xapi.html)
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
Apache-2.0
|
package/dist/index.cjs
CHANGED
|
@@ -29,30 +29,41 @@ module.exports = __toCommonJS(index_exports);
|
|
|
29
29
|
// src/queue.ts
|
|
30
30
|
function createInMemoryXAPIQueue() {
|
|
31
31
|
const buffer = [];
|
|
32
|
+
let flushInFlight = null;
|
|
33
|
+
const runFlush = async (transport) => {
|
|
34
|
+
while (buffer.length) {
|
|
35
|
+
const statement = buffer[0];
|
|
36
|
+
try {
|
|
37
|
+
await transport(statement);
|
|
38
|
+
buffer.shift();
|
|
39
|
+
} catch {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
32
44
|
return {
|
|
33
45
|
enqueue: (statement) => {
|
|
46
|
+
if (statement.id && buffer.some((s) => s.id === statement.id)) return;
|
|
34
47
|
buffer.push(statement);
|
|
35
48
|
},
|
|
36
49
|
size: () => buffer.length,
|
|
37
50
|
flush: async (transport) => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
51
|
+
if (flushInFlight) return flushInFlight;
|
|
52
|
+
if (!buffer.length) return;
|
|
53
|
+
flushInFlight = runFlush(transport).finally(() => {
|
|
54
|
+
flushInFlight = null;
|
|
55
|
+
});
|
|
56
|
+
return flushInFlight;
|
|
47
57
|
}
|
|
48
58
|
};
|
|
49
59
|
}
|
|
50
60
|
|
|
51
61
|
// src/client.ts
|
|
52
|
-
var
|
|
62
|
+
var import_core3 = require("@lessonkit/core");
|
|
53
63
|
|
|
54
64
|
// src/telemetryMap.ts
|
|
55
65
|
var import_core = require("@lessonkit/core");
|
|
66
|
+
var import_core2 = require("@lessonkit/core");
|
|
56
67
|
|
|
57
68
|
// src/id.ts
|
|
58
69
|
function cryptoRandomId() {
|
|
@@ -80,13 +91,13 @@ function telemetryEventToXAPIStatement(event) {
|
|
|
80
91
|
const { courseId } = event;
|
|
81
92
|
switch (event.name) {
|
|
82
93
|
case "course_started":
|
|
83
|
-
return statementFor((0,
|
|
94
|
+
return statementFor((0, import_core2.buildLessonkitUrn)({ courseId }), XAPIVerbs.initialized, event.timestamp);
|
|
84
95
|
case "course_completed":
|
|
85
|
-
return statementFor((0,
|
|
96
|
+
return statementFor((0, import_core2.buildLessonkitUrn)({ courseId }), XAPIVerbs.completed, event.timestamp);
|
|
86
97
|
case "lesson_started": {
|
|
87
98
|
const lessonId = event.lessonId;
|
|
88
99
|
return statementFor(
|
|
89
|
-
(0,
|
|
100
|
+
(0, import_core2.buildLessonkitUrn)({ courseId, lessonId }),
|
|
90
101
|
XAPIVerbs.initialized,
|
|
91
102
|
event.timestamp
|
|
92
103
|
);
|
|
@@ -109,7 +120,7 @@ function telemetryEventToXAPIStatement(event) {
|
|
|
109
120
|
scaled: typeof raw === "number" && typeof max === "number" && max > 0 ? raw / max : void 0
|
|
110
121
|
};
|
|
111
122
|
}
|
|
112
|
-
return statementFor((0,
|
|
123
|
+
return statementFor((0, import_core2.buildLessonkitUrn)({ courseId, lessonId }), XAPIVerbs.completed, event.timestamp, {
|
|
113
124
|
result: Object.keys(result).length ? result : void 0
|
|
114
125
|
});
|
|
115
126
|
}
|
|
@@ -118,10 +129,15 @@ function telemetryEventToXAPIStatement(event) {
|
|
|
118
129
|
case "quiz_answered": {
|
|
119
130
|
const lessonId = event.lessonId;
|
|
120
131
|
const checkId = event.data.checkId;
|
|
132
|
+
const result = {};
|
|
133
|
+
if (typeof event.data.correct === "boolean") {
|
|
134
|
+
result.success = event.data.correct;
|
|
135
|
+
}
|
|
121
136
|
return statementFor(
|
|
122
|
-
(0,
|
|
137
|
+
(0, import_core2.buildLessonkitUrn)({ courseId, lessonId, checkId }),
|
|
123
138
|
XAPIVerbs.answered,
|
|
124
|
-
event.timestamp
|
|
139
|
+
event.timestamp,
|
|
140
|
+
{ result: Object.keys(result).length ? result : void 0 }
|
|
125
141
|
);
|
|
126
142
|
}
|
|
127
143
|
case "quiz_completed": {
|
|
@@ -140,7 +156,7 @@ function telemetryEventToXAPIStatement(event) {
|
|
|
140
156
|
};
|
|
141
157
|
}
|
|
142
158
|
return statementFor(
|
|
143
|
-
(0,
|
|
159
|
+
(0, import_core2.buildLessonkitUrn)({ courseId, lessonId, checkId }),
|
|
144
160
|
XAPIVerbs.completed,
|
|
145
161
|
event.timestamp,
|
|
146
162
|
{ result: Object.keys(result).length ? result : void 0 }
|
|
@@ -151,13 +167,13 @@ function telemetryEventToXAPIStatement(event) {
|
|
|
151
167
|
const blockId = event.data?.blockId;
|
|
152
168
|
if (!lessonId || !blockId) return null;
|
|
153
169
|
return statementFor(
|
|
154
|
-
(0,
|
|
170
|
+
(0, import_core2.buildLessonkitUrn)({ courseId, lessonId, blockId }),
|
|
155
171
|
XAPIVerbs.experienced,
|
|
156
172
|
event.timestamp
|
|
157
173
|
);
|
|
158
174
|
}
|
|
159
175
|
default:
|
|
160
|
-
return
|
|
176
|
+
return (0, import_core.assertNever)(event, "Unhandled telemetry event");
|
|
161
177
|
}
|
|
162
178
|
}
|
|
163
179
|
function statementFor(objectId, verb, timestamp, extra) {
|
|
@@ -182,6 +198,7 @@ function createXAPIClient(opts) {
|
|
|
182
198
|
const queue = opts?.queue ?? createInMemoryXAPIQueue();
|
|
183
199
|
let warnedNoTransport = false;
|
|
184
200
|
let warnedTransportFailure = false;
|
|
201
|
+
const inflightById = /* @__PURE__ */ new Map();
|
|
185
202
|
const sendOrQueue = (statement) => {
|
|
186
203
|
if (!transport) {
|
|
187
204
|
queue.enqueue(statement);
|
|
@@ -193,7 +210,9 @@ function createXAPIClient(opts) {
|
|
|
193
210
|
}
|
|
194
211
|
return;
|
|
195
212
|
}
|
|
196
|
-
|
|
213
|
+
const existing = inflightById.get(statement.id);
|
|
214
|
+
if (existing) return;
|
|
215
|
+
const flight = Promise.resolve().then(() => transport(statement)).catch(() => {
|
|
197
216
|
queue.enqueue(statement);
|
|
198
217
|
if (isDevEnvironment() && !warnedTransportFailure) {
|
|
199
218
|
warnedTransportFailure = true;
|
|
@@ -201,7 +220,11 @@ function createXAPIClient(opts) {
|
|
|
201
220
|
"[lessonkit] xAPI transport failed; statement re-queued. Check your LRS endpoint or transport implementation."
|
|
202
221
|
);
|
|
203
222
|
}
|
|
223
|
+
}).finally(() => {
|
|
224
|
+
inflightById.delete(statement.id);
|
|
204
225
|
});
|
|
226
|
+
inflightById.set(statement.id, flight);
|
|
227
|
+
void flight;
|
|
205
228
|
};
|
|
206
229
|
const emit = (event) => {
|
|
207
230
|
const statement = telemetryEventToXAPIStatement(event);
|
|
@@ -215,12 +238,16 @@ function createXAPIClient(opts) {
|
|
|
215
238
|
flush: async () => {
|
|
216
239
|
if (!transport) return;
|
|
217
240
|
await queue.flush(transport);
|
|
241
|
+
const flights = [...inflightById.values()];
|
|
242
|
+
if (flights.length > 0) {
|
|
243
|
+
await Promise.allSettled(flights);
|
|
244
|
+
}
|
|
218
245
|
},
|
|
219
246
|
startedLesson: ({ lessonId }) => {
|
|
220
247
|
if (!courseId) return;
|
|
221
248
|
emit({
|
|
222
249
|
name: "lesson_started",
|
|
223
|
-
timestamp: (0,
|
|
250
|
+
timestamp: (0, import_core3.nowIso)(),
|
|
224
251
|
courseId,
|
|
225
252
|
lessonId,
|
|
226
253
|
data: { lessonId }
|
|
@@ -236,7 +263,7 @@ function createXAPIClient(opts) {
|
|
|
236
263
|
if (!courseId) return;
|
|
237
264
|
emit({
|
|
238
265
|
name: "lesson_completed",
|
|
239
|
-
timestamp: (0,
|
|
266
|
+
timestamp: (0, import_core3.nowIso)(),
|
|
240
267
|
courseId,
|
|
241
268
|
lessonId,
|
|
242
269
|
data: { lessonId, durationMs, score, maxScore, success }
|
|
@@ -246,7 +273,7 @@ function createXAPIClient(opts) {
|
|
|
246
273
|
if (!courseId) return;
|
|
247
274
|
emit({
|
|
248
275
|
name: "course_completed",
|
|
249
|
-
timestamp: (0,
|
|
276
|
+
timestamp: (0, import_core3.nowIso)(),
|
|
250
277
|
courseId
|
|
251
278
|
});
|
|
252
279
|
}
|
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:
|
|
24
|
+
verb: XAPIVerbIri;
|
|
7
25
|
object: {
|
|
8
26
|
id: string;
|
|
9
|
-
definition?:
|
|
27
|
+
definition?: XAPIObjectDefinition;
|
|
10
28
|
};
|
|
11
|
-
result?:
|
|
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:
|
|
24
|
+
verb: XAPIVerbIri;
|
|
7
25
|
object: {
|
|
8
26
|
id: string;
|
|
9
|
-
definition?:
|
|
27
|
+
definition?: XAPIObjectDefinition;
|
|
10
28
|
};
|
|
11
|
-
result?:
|
|
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
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
// src/queue.ts
|
|
2
2
|
function createInMemoryXAPIQueue() {
|
|
3
3
|
const buffer = [];
|
|
4
|
+
let flushInFlight = null;
|
|
5
|
+
const runFlush = async (transport) => {
|
|
6
|
+
while (buffer.length) {
|
|
7
|
+
const statement = buffer[0];
|
|
8
|
+
try {
|
|
9
|
+
await transport(statement);
|
|
10
|
+
buffer.shift();
|
|
11
|
+
} catch {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
4
16
|
return {
|
|
5
17
|
enqueue: (statement) => {
|
|
18
|
+
if (statement.id && buffer.some((s) => s.id === statement.id)) return;
|
|
6
19
|
buffer.push(statement);
|
|
7
20
|
},
|
|
8
21
|
size: () => buffer.length,
|
|
9
22
|
flush: async (transport) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
23
|
+
if (flushInFlight) return flushInFlight;
|
|
24
|
+
if (!buffer.length) return;
|
|
25
|
+
flushInFlight = runFlush(transport).finally(() => {
|
|
26
|
+
flushInFlight = null;
|
|
27
|
+
});
|
|
28
|
+
return flushInFlight;
|
|
19
29
|
}
|
|
20
30
|
};
|
|
21
31
|
}
|
|
@@ -24,6 +34,7 @@ function createInMemoryXAPIQueue() {
|
|
|
24
34
|
import { nowIso } from "@lessonkit/core";
|
|
25
35
|
|
|
26
36
|
// src/telemetryMap.ts
|
|
37
|
+
import { assertNever } from "@lessonkit/core";
|
|
27
38
|
import { buildLessonkitUrn } from "@lessonkit/core";
|
|
28
39
|
|
|
29
40
|
// src/id.ts
|
|
@@ -90,10 +101,15 @@ function telemetryEventToXAPIStatement(event) {
|
|
|
90
101
|
case "quiz_answered": {
|
|
91
102
|
const lessonId = event.lessonId;
|
|
92
103
|
const checkId = event.data.checkId;
|
|
104
|
+
const result = {};
|
|
105
|
+
if (typeof event.data.correct === "boolean") {
|
|
106
|
+
result.success = event.data.correct;
|
|
107
|
+
}
|
|
93
108
|
return statementFor(
|
|
94
109
|
buildLessonkitUrn({ courseId, lessonId, checkId }),
|
|
95
110
|
XAPIVerbs.answered,
|
|
96
|
-
event.timestamp
|
|
111
|
+
event.timestamp,
|
|
112
|
+
{ result: Object.keys(result).length ? result : void 0 }
|
|
97
113
|
);
|
|
98
114
|
}
|
|
99
115
|
case "quiz_completed": {
|
|
@@ -129,7 +145,7 @@ function telemetryEventToXAPIStatement(event) {
|
|
|
129
145
|
);
|
|
130
146
|
}
|
|
131
147
|
default:
|
|
132
|
-
return
|
|
148
|
+
return assertNever(event, "Unhandled telemetry event");
|
|
133
149
|
}
|
|
134
150
|
}
|
|
135
151
|
function statementFor(objectId, verb, timestamp, extra) {
|
|
@@ -154,6 +170,7 @@ function createXAPIClient(opts) {
|
|
|
154
170
|
const queue = opts?.queue ?? createInMemoryXAPIQueue();
|
|
155
171
|
let warnedNoTransport = false;
|
|
156
172
|
let warnedTransportFailure = false;
|
|
173
|
+
const inflightById = /* @__PURE__ */ new Map();
|
|
157
174
|
const sendOrQueue = (statement) => {
|
|
158
175
|
if (!transport) {
|
|
159
176
|
queue.enqueue(statement);
|
|
@@ -165,7 +182,9 @@ function createXAPIClient(opts) {
|
|
|
165
182
|
}
|
|
166
183
|
return;
|
|
167
184
|
}
|
|
168
|
-
|
|
185
|
+
const existing = inflightById.get(statement.id);
|
|
186
|
+
if (existing) return;
|
|
187
|
+
const flight = Promise.resolve().then(() => transport(statement)).catch(() => {
|
|
169
188
|
queue.enqueue(statement);
|
|
170
189
|
if (isDevEnvironment() && !warnedTransportFailure) {
|
|
171
190
|
warnedTransportFailure = true;
|
|
@@ -173,7 +192,11 @@ function createXAPIClient(opts) {
|
|
|
173
192
|
"[lessonkit] xAPI transport failed; statement re-queued. Check your LRS endpoint or transport implementation."
|
|
174
193
|
);
|
|
175
194
|
}
|
|
195
|
+
}).finally(() => {
|
|
196
|
+
inflightById.delete(statement.id);
|
|
176
197
|
});
|
|
198
|
+
inflightById.set(statement.id, flight);
|
|
199
|
+
void flight;
|
|
177
200
|
};
|
|
178
201
|
const emit = (event) => {
|
|
179
202
|
const statement = telemetryEventToXAPIStatement(event);
|
|
@@ -187,6 +210,10 @@ function createXAPIClient(opts) {
|
|
|
187
210
|
flush: async () => {
|
|
188
211
|
if (!transport) return;
|
|
189
212
|
await queue.flush(transport);
|
|
213
|
+
const flights = [...inflightById.values()];
|
|
214
|
+
if (flights.length > 0) {
|
|
215
|
+
await Promise.allSettled(flights);
|
|
216
|
+
}
|
|
190
217
|
},
|
|
191
218
|
startedLesson: ({ lessonId }) => {
|
|
192
219
|
if (!courseId) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lessonkit/xapi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "xAPI statement generation primitives for LessonKit.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
"training",
|
|
22
22
|
"lrs"
|
|
23
23
|
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
24
27
|
"type": "module",
|
|
25
28
|
"main": "./dist/index.cjs",
|
|
26
29
|
"module": "./dist/index.js",
|
|
@@ -45,7 +48,7 @@
|
|
|
45
48
|
"lint": "echo \"(no lint configured yet)\""
|
|
46
49
|
},
|
|
47
50
|
"dependencies": {
|
|
48
|
-
"@lessonkit/core": "0.
|
|
51
|
+
"@lessonkit/core": "1.0.1"
|
|
49
52
|
},
|
|
50
53
|
"devDependencies": {
|
|
51
54
|
"tsup": "^8.5.0",
|