@lessonkit/xapi 1.2.0 → 1.3.0
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 +82 -31
- package/dist/index.js +82 -31
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -26,35 +26,64 @@ __export(index_exports, {
|
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(index_exports);
|
|
28
28
|
|
|
29
|
+
// src/id.ts
|
|
30
|
+
function cryptoRandomId() {
|
|
31
|
+
const g = globalThis;
|
|
32
|
+
if (g.crypto?.randomUUID) return g.crypto.randomUUID();
|
|
33
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
29
36
|
// src/queue.ts
|
|
37
|
+
function withStatementId(statement) {
|
|
38
|
+
const trimmed = statement.id?.trim();
|
|
39
|
+
if (trimmed) {
|
|
40
|
+
if (trimmed !== statement.id) statement.id = trimmed;
|
|
41
|
+
return statement;
|
|
42
|
+
}
|
|
43
|
+
statement.id = cryptoRandomId();
|
|
44
|
+
return statement;
|
|
45
|
+
}
|
|
30
46
|
var DEFAULT_MAX_QUEUE_SIZE = 1e3;
|
|
31
47
|
function createInMemoryXAPIQueue(opts) {
|
|
32
48
|
const maxSize = opts?.maxSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
33
49
|
const buffer = [];
|
|
34
50
|
let flushInFlight = null;
|
|
51
|
+
let headInFlight = false;
|
|
35
52
|
const notifyDepth = () => {
|
|
36
53
|
opts?.onDepth?.(buffer.length);
|
|
37
54
|
};
|
|
38
55
|
const runFlush = async (transport) => {
|
|
39
56
|
while (buffer.length) {
|
|
40
57
|
const statement = buffer[0];
|
|
58
|
+
headInFlight = true;
|
|
41
59
|
try {
|
|
42
60
|
await transport(statement);
|
|
43
61
|
buffer.shift();
|
|
44
62
|
notifyDepth();
|
|
45
63
|
} catch {
|
|
46
64
|
return;
|
|
65
|
+
} finally {
|
|
66
|
+
headInFlight = false;
|
|
47
67
|
}
|
|
48
68
|
}
|
|
49
69
|
};
|
|
50
70
|
return {
|
|
51
71
|
enqueue: (statement) => {
|
|
52
|
-
|
|
72
|
+
const normalized = withStatementId(statement);
|
|
73
|
+
if (buffer.some((s) => s.id === normalized.id)) return;
|
|
53
74
|
if (buffer.length >= maxSize) {
|
|
54
|
-
buffer.
|
|
75
|
+
if (headInFlight && buffer.length <= 1) {
|
|
76
|
+
opts?.onCap?.();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (headInFlight) {
|
|
80
|
+
buffer.splice(1, 1);
|
|
81
|
+
} else {
|
|
82
|
+
buffer.shift();
|
|
83
|
+
}
|
|
55
84
|
opts?.onCap?.();
|
|
56
85
|
}
|
|
57
|
-
buffer.push(
|
|
86
|
+
buffer.push(normalized);
|
|
58
87
|
notifyDepth();
|
|
59
88
|
},
|
|
60
89
|
size: () => buffer.length,
|
|
@@ -75,16 +104,10 @@ var import_core2 = require("@lessonkit/core");
|
|
|
75
104
|
// src/telemetryMap.ts
|
|
76
105
|
var import_core = require("@lessonkit/core");
|
|
77
106
|
|
|
78
|
-
// src/id.ts
|
|
79
|
-
function cryptoRandomId() {
|
|
80
|
-
const g = globalThis;
|
|
81
|
-
if (g.crypto?.randomUUID) return g.crypto.randomUUID();
|
|
82
|
-
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
107
|
// src/duration.ts
|
|
86
108
|
function formatDurationMs(ms) {
|
|
87
|
-
|
|
109
|
+
if (!Number.isFinite(ms) || ms < 0) return void 0;
|
|
110
|
+
const safe = ms;
|
|
88
111
|
const seconds = safe / 1e3;
|
|
89
112
|
const fixed = Number.isInteger(seconds) ? String(seconds) : seconds.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
|
|
90
113
|
return `PT${fixed}S`;
|
|
@@ -101,12 +124,18 @@ function buildXapiScoreResult(opts) {
|
|
|
101
124
|
const max = typeof opts.maxScore === "number" ? opts.maxScore : void 0;
|
|
102
125
|
const raw = typeof opts.score === "number" ? opts.score : void 0;
|
|
103
126
|
if (typeof raw !== "number" && typeof max !== "number") return void 0;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
};
|
|
127
|
+
if (typeof raw === "number" && !Number.isFinite(raw) || typeof max === "number" && !Number.isFinite(max)) {
|
|
128
|
+
return void 0;
|
|
129
|
+
}
|
|
130
|
+
if (typeof max === "number" && max <= 0) return void 0;
|
|
131
|
+
if (typeof raw === "number" && raw < 0) return void 0;
|
|
132
|
+
const result = { min: 0 };
|
|
133
|
+
if (typeof raw === "number") result.raw = raw;
|
|
134
|
+
if (typeof max === "number") result.max = max;
|
|
135
|
+
if (typeof raw === "number" && typeof max === "number" && max > 0 && raw <= max) {
|
|
136
|
+
result.scaled = raw / max;
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
110
139
|
}
|
|
111
140
|
function statementFor(objectId, verb, timestamp, extra) {
|
|
112
141
|
return {
|
|
@@ -129,7 +158,7 @@ var experiencedBlockMapper = (event, ctx) => {
|
|
|
129
158
|
if (event.name === "interaction") {
|
|
130
159
|
const lessonId2 = event.lessonId;
|
|
131
160
|
const blockId2 = event.data?.blockId;
|
|
132
|
-
if (!lessonId2 || !blockId2) return null;
|
|
161
|
+
if (!lessonId2 || !blockId2 || typeof blockId2 !== "string") return null;
|
|
133
162
|
return experiencedBlockStatement(ctx.courseId, lessonId2, blockId2, ctx.timestamp);
|
|
134
163
|
}
|
|
135
164
|
const lessonId = event.lessonId;
|
|
@@ -155,7 +184,8 @@ var TELEMETRY_XAPI_MAPPERS = {
|
|
|
155
184
|
const data = event.data;
|
|
156
185
|
const result = {};
|
|
157
186
|
if (typeof data?.durationMs === "number") {
|
|
158
|
-
|
|
187
|
+
const duration = formatDurationMs(data.durationMs);
|
|
188
|
+
if (duration !== void 0) result.duration = duration;
|
|
159
189
|
}
|
|
160
190
|
if (typeof data?.success === "boolean") result.success = data.success;
|
|
161
191
|
const score = buildXapiScoreResult({ score: data?.score, maxScore: data?.maxScore });
|
|
@@ -209,6 +239,7 @@ var TELEMETRY_XAPI_MAPPERS = {
|
|
|
209
239
|
},
|
|
210
240
|
interaction: experiencedBlockMapper,
|
|
211
241
|
book_page_viewed: experiencedBlockMapper,
|
|
242
|
+
slide_viewed: experiencedBlockMapper,
|
|
212
243
|
compound_page_viewed: experiencedBlockMapper,
|
|
213
244
|
hotspot_opened: experiencedBlockMapper,
|
|
214
245
|
accordion_section_toggled: experiencedBlockMapper,
|
|
@@ -227,6 +258,15 @@ function telemetryEventToXAPIStatement(event) {
|
|
|
227
258
|
}
|
|
228
259
|
|
|
229
260
|
// src/client.ts
|
|
261
|
+
function withStatementId2(statement) {
|
|
262
|
+
const trimmed = statement.id?.trim();
|
|
263
|
+
if (trimmed) {
|
|
264
|
+
if (trimmed !== statement.id) statement.id = trimmed;
|
|
265
|
+
return statement;
|
|
266
|
+
}
|
|
267
|
+
statement.id = cryptoRandomId();
|
|
268
|
+
return statement;
|
|
269
|
+
}
|
|
230
270
|
function isDevEnvironment() {
|
|
231
271
|
const g = globalThis;
|
|
232
272
|
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
@@ -243,8 +283,9 @@ function createXAPIClient(opts) {
|
|
|
243
283
|
let warnedTransportFailure = false;
|
|
244
284
|
const inflightById = /* @__PURE__ */ new Map();
|
|
245
285
|
const sendOrQueue = (statement) => {
|
|
286
|
+
const normalized = withStatementId2(statement);
|
|
246
287
|
if (!transport) {
|
|
247
|
-
queue.enqueue(
|
|
288
|
+
queue.enqueue(normalized);
|
|
248
289
|
if (isDevEnvironment() && !warnedNoTransport) {
|
|
249
290
|
warnedNoTransport = true;
|
|
250
291
|
console.warn(
|
|
@@ -253,35 +294,45 @@ function createXAPIClient(opts) {
|
|
|
253
294
|
}
|
|
254
295
|
return;
|
|
255
296
|
}
|
|
256
|
-
const existing = inflightById.get(
|
|
297
|
+
const existing = inflightById.get(normalized.id);
|
|
257
298
|
if (existing) {
|
|
258
299
|
void existing.then(
|
|
259
300
|
() => void 0,
|
|
260
301
|
() => {
|
|
261
|
-
sendOrQueue(
|
|
302
|
+
sendOrQueue(normalized);
|
|
262
303
|
}
|
|
263
304
|
);
|
|
264
305
|
return;
|
|
265
306
|
}
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
queue.enqueue(statement);
|
|
307
|
+
const flight = Promise.resolve().then(() => transport(normalized)).catch(() => {
|
|
308
|
+
queue.enqueue(normalized);
|
|
269
309
|
if (isDevEnvironment() && !warnedTransportFailure) {
|
|
270
310
|
warnedTransportFailure = true;
|
|
271
311
|
console.warn(
|
|
272
312
|
"[lessonkit] xAPI transport failed; statement re-queued. Check your LRS endpoint or transport implementation."
|
|
273
313
|
);
|
|
274
314
|
}
|
|
315
|
+
throw new Error("xAPI transport failed");
|
|
275
316
|
}).finally(() => {
|
|
276
|
-
inflightById.delete(
|
|
317
|
+
inflightById.delete(normalized.id);
|
|
318
|
+
});
|
|
319
|
+
inflightById.set(normalized.id, flight);
|
|
320
|
+
void flight.catch(() => {
|
|
277
321
|
});
|
|
278
|
-
inflightById.set(statement.id, transportFlight);
|
|
279
|
-
void flight;
|
|
280
322
|
};
|
|
281
323
|
const emit = (event) => {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
324
|
+
try {
|
|
325
|
+
const statement = telemetryEventToXAPIStatement(event);
|
|
326
|
+
if (!statement) return;
|
|
327
|
+
sendOrQueue(statement);
|
|
328
|
+
} catch (err) {
|
|
329
|
+
if (isDevEnvironment()) {
|
|
330
|
+
console.warn(
|
|
331
|
+
"[lessonkit] xAPI mapping skipped:",
|
|
332
|
+
err instanceof Error ? err.message : err
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
285
336
|
};
|
|
286
337
|
return {
|
|
287
338
|
send: (statement) => {
|
package/dist/index.js
CHANGED
|
@@ -1,32 +1,61 @@
|
|
|
1
|
+
// src/id.ts
|
|
2
|
+
function cryptoRandomId() {
|
|
3
|
+
const g = globalThis;
|
|
4
|
+
if (g.crypto?.randomUUID) return g.crypto.randomUUID();
|
|
5
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
|
|
6
|
+
}
|
|
7
|
+
|
|
1
8
|
// src/queue.ts
|
|
9
|
+
function withStatementId(statement) {
|
|
10
|
+
const trimmed = statement.id?.trim();
|
|
11
|
+
if (trimmed) {
|
|
12
|
+
if (trimmed !== statement.id) statement.id = trimmed;
|
|
13
|
+
return statement;
|
|
14
|
+
}
|
|
15
|
+
statement.id = cryptoRandomId();
|
|
16
|
+
return statement;
|
|
17
|
+
}
|
|
2
18
|
var DEFAULT_MAX_QUEUE_SIZE = 1e3;
|
|
3
19
|
function createInMemoryXAPIQueue(opts) {
|
|
4
20
|
const maxSize = opts?.maxSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
5
21
|
const buffer = [];
|
|
6
22
|
let flushInFlight = null;
|
|
23
|
+
let headInFlight = false;
|
|
7
24
|
const notifyDepth = () => {
|
|
8
25
|
opts?.onDepth?.(buffer.length);
|
|
9
26
|
};
|
|
10
27
|
const runFlush = async (transport) => {
|
|
11
28
|
while (buffer.length) {
|
|
12
29
|
const statement = buffer[0];
|
|
30
|
+
headInFlight = true;
|
|
13
31
|
try {
|
|
14
32
|
await transport(statement);
|
|
15
33
|
buffer.shift();
|
|
16
34
|
notifyDepth();
|
|
17
35
|
} catch {
|
|
18
36
|
return;
|
|
37
|
+
} finally {
|
|
38
|
+
headInFlight = false;
|
|
19
39
|
}
|
|
20
40
|
}
|
|
21
41
|
};
|
|
22
42
|
return {
|
|
23
43
|
enqueue: (statement) => {
|
|
24
|
-
|
|
44
|
+
const normalized = withStatementId(statement);
|
|
45
|
+
if (buffer.some((s) => s.id === normalized.id)) return;
|
|
25
46
|
if (buffer.length >= maxSize) {
|
|
26
|
-
buffer.
|
|
47
|
+
if (headInFlight && buffer.length <= 1) {
|
|
48
|
+
opts?.onCap?.();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (headInFlight) {
|
|
52
|
+
buffer.splice(1, 1);
|
|
53
|
+
} else {
|
|
54
|
+
buffer.shift();
|
|
55
|
+
}
|
|
27
56
|
opts?.onCap?.();
|
|
28
57
|
}
|
|
29
|
-
buffer.push(
|
|
58
|
+
buffer.push(normalized);
|
|
30
59
|
notifyDepth();
|
|
31
60
|
},
|
|
32
61
|
size: () => buffer.length,
|
|
@@ -47,16 +76,10 @@ import { nowIso } from "@lessonkit/core";
|
|
|
47
76
|
// src/telemetryMap.ts
|
|
48
77
|
import { buildLessonkitUrn } from "@lessonkit/core";
|
|
49
78
|
|
|
50
|
-
// src/id.ts
|
|
51
|
-
function cryptoRandomId() {
|
|
52
|
-
const g = globalThis;
|
|
53
|
-
if (g.crypto?.randomUUID) return g.crypto.randomUUID();
|
|
54
|
-
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
79
|
// src/duration.ts
|
|
58
80
|
function formatDurationMs(ms) {
|
|
59
|
-
|
|
81
|
+
if (!Number.isFinite(ms) || ms < 0) return void 0;
|
|
82
|
+
const safe = ms;
|
|
60
83
|
const seconds = safe / 1e3;
|
|
61
84
|
const fixed = Number.isInteger(seconds) ? String(seconds) : seconds.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
|
|
62
85
|
return `PT${fixed}S`;
|
|
@@ -73,12 +96,18 @@ function buildXapiScoreResult(opts) {
|
|
|
73
96
|
const max = typeof opts.maxScore === "number" ? opts.maxScore : void 0;
|
|
74
97
|
const raw = typeof opts.score === "number" ? opts.score : void 0;
|
|
75
98
|
if (typeof raw !== "number" && typeof max !== "number") return void 0;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
};
|
|
99
|
+
if (typeof raw === "number" && !Number.isFinite(raw) || typeof max === "number" && !Number.isFinite(max)) {
|
|
100
|
+
return void 0;
|
|
101
|
+
}
|
|
102
|
+
if (typeof max === "number" && max <= 0) return void 0;
|
|
103
|
+
if (typeof raw === "number" && raw < 0) return void 0;
|
|
104
|
+
const result = { min: 0 };
|
|
105
|
+
if (typeof raw === "number") result.raw = raw;
|
|
106
|
+
if (typeof max === "number") result.max = max;
|
|
107
|
+
if (typeof raw === "number" && typeof max === "number" && max > 0 && raw <= max) {
|
|
108
|
+
result.scaled = raw / max;
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
82
111
|
}
|
|
83
112
|
function statementFor(objectId, verb, timestamp, extra) {
|
|
84
113
|
return {
|
|
@@ -101,7 +130,7 @@ var experiencedBlockMapper = (event, ctx) => {
|
|
|
101
130
|
if (event.name === "interaction") {
|
|
102
131
|
const lessonId2 = event.lessonId;
|
|
103
132
|
const blockId2 = event.data?.blockId;
|
|
104
|
-
if (!lessonId2 || !blockId2) return null;
|
|
133
|
+
if (!lessonId2 || !blockId2 || typeof blockId2 !== "string") return null;
|
|
105
134
|
return experiencedBlockStatement(ctx.courseId, lessonId2, blockId2, ctx.timestamp);
|
|
106
135
|
}
|
|
107
136
|
const lessonId = event.lessonId;
|
|
@@ -127,7 +156,8 @@ var TELEMETRY_XAPI_MAPPERS = {
|
|
|
127
156
|
const data = event.data;
|
|
128
157
|
const result = {};
|
|
129
158
|
if (typeof data?.durationMs === "number") {
|
|
130
|
-
|
|
159
|
+
const duration = formatDurationMs(data.durationMs);
|
|
160
|
+
if (duration !== void 0) result.duration = duration;
|
|
131
161
|
}
|
|
132
162
|
if (typeof data?.success === "boolean") result.success = data.success;
|
|
133
163
|
const score = buildXapiScoreResult({ score: data?.score, maxScore: data?.maxScore });
|
|
@@ -181,6 +211,7 @@ var TELEMETRY_XAPI_MAPPERS = {
|
|
|
181
211
|
},
|
|
182
212
|
interaction: experiencedBlockMapper,
|
|
183
213
|
book_page_viewed: experiencedBlockMapper,
|
|
214
|
+
slide_viewed: experiencedBlockMapper,
|
|
184
215
|
compound_page_viewed: experiencedBlockMapper,
|
|
185
216
|
hotspot_opened: experiencedBlockMapper,
|
|
186
217
|
accordion_section_toggled: experiencedBlockMapper,
|
|
@@ -199,6 +230,15 @@ function telemetryEventToXAPIStatement(event) {
|
|
|
199
230
|
}
|
|
200
231
|
|
|
201
232
|
// src/client.ts
|
|
233
|
+
function withStatementId2(statement) {
|
|
234
|
+
const trimmed = statement.id?.trim();
|
|
235
|
+
if (trimmed) {
|
|
236
|
+
if (trimmed !== statement.id) statement.id = trimmed;
|
|
237
|
+
return statement;
|
|
238
|
+
}
|
|
239
|
+
statement.id = cryptoRandomId();
|
|
240
|
+
return statement;
|
|
241
|
+
}
|
|
202
242
|
function isDevEnvironment() {
|
|
203
243
|
const g = globalThis;
|
|
204
244
|
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
@@ -215,8 +255,9 @@ function createXAPIClient(opts) {
|
|
|
215
255
|
let warnedTransportFailure = false;
|
|
216
256
|
const inflightById = /* @__PURE__ */ new Map();
|
|
217
257
|
const sendOrQueue = (statement) => {
|
|
258
|
+
const normalized = withStatementId2(statement);
|
|
218
259
|
if (!transport) {
|
|
219
|
-
queue.enqueue(
|
|
260
|
+
queue.enqueue(normalized);
|
|
220
261
|
if (isDevEnvironment() && !warnedNoTransport) {
|
|
221
262
|
warnedNoTransport = true;
|
|
222
263
|
console.warn(
|
|
@@ -225,35 +266,45 @@ function createXAPIClient(opts) {
|
|
|
225
266
|
}
|
|
226
267
|
return;
|
|
227
268
|
}
|
|
228
|
-
const existing = inflightById.get(
|
|
269
|
+
const existing = inflightById.get(normalized.id);
|
|
229
270
|
if (existing) {
|
|
230
271
|
void existing.then(
|
|
231
272
|
() => void 0,
|
|
232
273
|
() => {
|
|
233
|
-
sendOrQueue(
|
|
274
|
+
sendOrQueue(normalized);
|
|
234
275
|
}
|
|
235
276
|
);
|
|
236
277
|
return;
|
|
237
278
|
}
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
queue.enqueue(statement);
|
|
279
|
+
const flight = Promise.resolve().then(() => transport(normalized)).catch(() => {
|
|
280
|
+
queue.enqueue(normalized);
|
|
241
281
|
if (isDevEnvironment() && !warnedTransportFailure) {
|
|
242
282
|
warnedTransportFailure = true;
|
|
243
283
|
console.warn(
|
|
244
284
|
"[lessonkit] xAPI transport failed; statement re-queued. Check your LRS endpoint or transport implementation."
|
|
245
285
|
);
|
|
246
286
|
}
|
|
287
|
+
throw new Error("xAPI transport failed");
|
|
247
288
|
}).finally(() => {
|
|
248
|
-
inflightById.delete(
|
|
289
|
+
inflightById.delete(normalized.id);
|
|
290
|
+
});
|
|
291
|
+
inflightById.set(normalized.id, flight);
|
|
292
|
+
void flight.catch(() => {
|
|
249
293
|
});
|
|
250
|
-
inflightById.set(statement.id, transportFlight);
|
|
251
|
-
void flight;
|
|
252
294
|
};
|
|
253
295
|
const emit = (event) => {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
296
|
+
try {
|
|
297
|
+
const statement = telemetryEventToXAPIStatement(event);
|
|
298
|
+
if (!statement) return;
|
|
299
|
+
sendOrQueue(statement);
|
|
300
|
+
} catch (err) {
|
|
301
|
+
if (isDevEnvironment()) {
|
|
302
|
+
console.warn(
|
|
303
|
+
"[lessonkit] xAPI mapping skipped:",
|
|
304
|
+
err instanceof Error ? err.message : err
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
257
308
|
};
|
|
258
309
|
return {
|
|
259
310
|
send: (statement) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lessonkit/xapi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "xAPI statement generation primitives for LessonKit.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"lint": "echo \"(no lint configured yet)\""
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@lessonkit/core": "1.
|
|
51
|
+
"@lessonkit/core": "1.3.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"tsup": "^8.5.0",
|