@contractspec/example.learning-journey-registry 1.57.0 → 1.58.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.
Files changed (77) hide show
  1. package/.turbo/turbo-build.log +52 -55
  2. package/.turbo/turbo-prebuild.log +1 -0
  3. package/CHANGELOG.md +23 -0
  4. package/dist/api-types.d.ts +33 -37
  5. package/dist/api-types.d.ts.map +1 -1
  6. package/dist/api-types.js +1 -0
  7. package/dist/api.d.ts +7 -11
  8. package/dist/api.d.ts.map +1 -1
  9. package/dist/api.js +261 -161
  10. package/dist/api.test.d.ts +2 -0
  11. package/dist/api.test.d.ts.map +1 -0
  12. package/dist/browser/api-types.js +0 -0
  13. package/dist/browser/api.js +270 -0
  14. package/dist/browser/docs/index.js +33 -0
  15. package/dist/browser/docs/learning-journey-registry.docblock.js +33 -0
  16. package/dist/browser/example.js +32 -0
  17. package/dist/browser/index.js +540 -0
  18. package/dist/browser/learning-journey-registry.feature.js +45 -0
  19. package/dist/browser/presentations/index.js +67 -0
  20. package/dist/browser/progress-store.js +34 -0
  21. package/dist/browser/tracks.js +52 -0
  22. package/dist/browser/ui/LearningMiniApp.js +133 -0
  23. package/dist/browser/ui/index.js +133 -0
  24. package/dist/docs/index.d.ts +2 -1
  25. package/dist/docs/index.d.ts.map +1 -0
  26. package/dist/docs/index.js +34 -1
  27. package/dist/docs/learning-journey-registry.docblock.d.ts +2 -1
  28. package/dist/docs/learning-journey-registry.docblock.d.ts.map +1 -0
  29. package/dist/docs/learning-journey-registry.docblock.js +18 -22
  30. package/dist/example.d.ts +2 -6
  31. package/dist/example.d.ts.map +1 -1
  32. package/dist/example.js +31 -39
  33. package/dist/index.d.ts +8 -8
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +541 -10
  36. package/dist/learning-journey-registry.feature.d.ts +1 -6
  37. package/dist/learning-journey-registry.feature.d.ts.map +1 -1
  38. package/dist/learning-journey-registry.feature.js +44 -73
  39. package/dist/node/api-types.js +0 -0
  40. package/dist/node/api.js +270 -0
  41. package/dist/node/docs/index.js +33 -0
  42. package/dist/node/docs/learning-journey-registry.docblock.js +33 -0
  43. package/dist/node/example.js +32 -0
  44. package/dist/node/index.js +540 -0
  45. package/dist/node/learning-journey-registry.feature.js +45 -0
  46. package/dist/node/presentations/index.js +67 -0
  47. package/dist/node/progress-store.js +34 -0
  48. package/dist/node/tracks.js +52 -0
  49. package/dist/node/ui/LearningMiniApp.js +133 -0
  50. package/dist/node/ui/index.js +133 -0
  51. package/dist/presentations/index.d.ts +4 -9
  52. package/dist/presentations/index.d.ts.map +1 -1
  53. package/dist/presentations/index.js +63 -66
  54. package/dist/progress-store.d.ts +6 -10
  55. package/dist/progress-store.d.ts.map +1 -1
  56. package/dist/progress-store.js +33 -29
  57. package/dist/tracks.d.ts +33 -36
  58. package/dist/tracks.d.ts.map +1 -1
  59. package/dist/tracks.js +44 -39
  60. package/dist/ui/LearningMiniApp.d.ts +8 -16
  61. package/dist/ui/LearningMiniApp.d.ts.map +1 -1
  62. package/dist/ui/LearningMiniApp.js +123 -69
  63. package/dist/ui/index.d.ts +2 -2
  64. package/dist/ui/index.d.ts.map +1 -0
  65. package/dist/ui/index.js +134 -3
  66. package/package.json +143 -48
  67. package/tsdown.config.js +1 -2
  68. package/.turbo/turbo-build$colon$bundle.log +0 -55
  69. package/dist/api.js.map +0 -1
  70. package/dist/docs/learning-journey-registry.docblock.js.map +0 -1
  71. package/dist/example.js.map +0 -1
  72. package/dist/learning-journey-registry.feature.js.map +0 -1
  73. package/dist/presentations/index.js.map +0 -1
  74. package/dist/progress-store.js.map +0 -1
  75. package/dist/tracks.js.map +0 -1
  76. package/dist/ui/LearningMiniApp.js.map +0 -1
  77. package/tsconfig.tsbuildinfo +0 -1
package/dist/api.js CHANGED
@@ -1,171 +1,271 @@
1
- import { learningJourneyTracks } from "./tracks.js";
2
- import { getLearnerTracks, getTrackResolver, initProgress } from "./progress-store.js";
1
+ // @bun
2
+ // src/tracks.ts
3
+ import { crmLearningTracks } from "@contractspec/example.learning-journey-crm-onboarding/track";
4
+ import { drillTracks } from "@contractspec/example.learning-journey-duo-drills/track";
5
+ import { ambientCoachTracks } from "@contractspec/example.learning-journey-ambient-coach/track";
6
+ import { questTracks } from "@contractspec/example.learning-journey-quest-challenges/track";
7
+ import { platformLearningTracks } from "@contractspec/example.learning-journey-platform-tour/track";
8
+ import { studioLearningTracks } from "@contractspec/example.learning-journey-studio-onboarding/track";
9
+ var mapStep = (step) => ({
10
+ id: step.id,
11
+ title: step.title,
12
+ description: step.description,
13
+ completionEvent: step.completion.eventName,
14
+ completionCondition: step.completion,
15
+ xpReward: step.xpReward,
16
+ isRequired: step.isRequired,
17
+ canSkip: step.canSkip,
18
+ actionUrl: step.actionUrl,
19
+ actionLabel: step.actionLabel,
20
+ availability: step.availability,
21
+ metadata: step.metadata
22
+ });
23
+ var mapTrackSpecToDto = (track) => ({
24
+ id: track.id,
25
+ name: track.name,
26
+ description: track.description,
27
+ productId: track.productId,
28
+ targetUserSegment: track.targetUserSegment,
29
+ targetRole: track.targetRole,
30
+ totalXp: track.totalXp,
31
+ streakRule: track.streakRule,
32
+ completionRewards: track.completionRewards,
33
+ steps: track.steps.map(mapStep),
34
+ metadata: track.metadata
35
+ });
36
+ var learningJourneyTracks = [
37
+ ...studioLearningTracks,
38
+ ...platformLearningTracks,
39
+ ...crmLearningTracks,
40
+ ...drillTracks,
41
+ ...ambientCoachTracks,
42
+ ...questTracks
43
+ ];
44
+ var onboardingTrackCatalog = learningJourneyTracks.map(mapTrackSpecToDto);
45
+ // src/progress-store.ts
46
+ var progressStore = new Map;
47
+ var getTrackResolver = (tracks) => (trackId) => tracks.find((t) => t.id === trackId);
48
+ var getLearnerTracks = (learnerId) => {
49
+ const existing = progressStore.get(learnerId);
50
+ if (existing)
51
+ return existing;
52
+ const map = new Map;
53
+ progressStore.set(learnerId, map);
54
+ return map;
55
+ };
56
+ var initProgress = (learnerId, track) => ({
57
+ learnerId,
58
+ trackId: track.id,
59
+ progress: 0,
60
+ isCompleted: false,
61
+ xpEarned: 0,
62
+ steps: track.steps.map((step) => ({
63
+ id: step.id,
64
+ status: "PENDING",
65
+ xpEarned: 0,
66
+ occurrences: 0,
67
+ masteryCount: 0
68
+ })),
69
+ startedAt: undefined,
70
+ completedAt: undefined,
71
+ lastActivityAt: undefined
72
+ });
3
73
 
4
- //#region src/api.ts
5
- const getTrack = getTrackResolver(learningJourneyTracks);
6
- const matchesFilter = (filter, payload) => {
7
- if (!filter) return true;
8
- if (!payload) return false;
9
- return Object.entries(filter).every(([key, value]) => payload[key] === value);
74
+ // src/api.ts
75
+ var getTrack = getTrackResolver(learningJourneyTracks);
76
+ var matchesFilter = (filter, payload) => {
77
+ if (!filter)
78
+ return true;
79
+ if (!payload)
80
+ return false;
81
+ return Object.entries(filter).every(([key, value]) => payload[key] === value);
10
82
  };
11
- const matchesBaseEvent = (condition, event) => {
12
- if (condition.eventName !== event.name) return false;
13
- if (condition.eventVersion !== void 0 && event.version !== void 0) {
14
- if (condition.eventVersion !== event.version) return false;
15
- }
16
- if (condition.sourceModule && event.sourceModule && condition.sourceModule !== event.sourceModule) return false;
17
- return matchesFilter(condition.payloadFilter, event.payload);
83
+ var matchesBaseEvent = (condition, event) => {
84
+ if (condition.eventName !== event.name)
85
+ return false;
86
+ if (condition.eventVersion !== undefined && event.version !== undefined) {
87
+ if (condition.eventVersion !== event.version)
88
+ return false;
89
+ }
90
+ if (condition.sourceModule && event.sourceModule && condition.sourceModule !== event.sourceModule) {
91
+ return false;
92
+ }
93
+ return matchesFilter(condition.payloadFilter, event.payload);
18
94
  };
19
- const matchesCondition = (condition, event, step, trackStartedAt) => {
20
- if (condition.kind === "count") {
21
- if (!matchesBaseEvent(condition, event)) return { matched: false };
22
- const occurrences = (step.occurrences ?? 0) + 1;
23
- return {
24
- matched: (condition.withinHours === void 0 || Boolean(trackStartedAt && event.occurredAt && (event.occurredAt.getTime() - trackStartedAt.getTime()) / (1e3 * 60 * 60) <= condition.withinHours)) && occurrences >= condition.atLeast,
25
- occurrences
26
- };
27
- }
28
- if (condition.kind === "time_window") {
29
- if (!matchesBaseEvent(condition, event)) return { matched: false };
30
- if (condition.withinHoursOfStart !== void 0 && trackStartedAt && event.occurredAt) {
31
- if ((event.occurredAt.getTime() - trackStartedAt.getTime()) / (1e3 * 60 * 60) > condition.withinHoursOfStart) return { matched: false };
32
- }
33
- return { matched: true };
34
- }
35
- if (condition.kind === "srs_mastery") {
36
- if (event.name !== condition.eventName) return { matched: false };
37
- const payload = event.payload;
38
- if (!matchesFilter(condition.payloadFilter, payload)) return { matched: false };
39
- const skillKey = condition.skillIdField ?? "skillId";
40
- const masteryKey = condition.masteryField ?? "mastery";
41
- const skillId = payload?.[skillKey];
42
- const masteryValue = payload?.[masteryKey];
43
- if (skillId === void 0 || masteryValue === void 0) return { matched: false };
44
- if (typeof masteryValue !== "number") return { matched: false };
45
- if (masteryValue < condition.minimumMastery) return { matched: false };
46
- const masteryCount = (step.masteryCount ?? 0) + 1;
47
- return {
48
- matched: masteryCount >= (condition.requiredCount ?? 1),
49
- masteryCount
50
- };
51
- }
52
- return { matched: matchesBaseEvent(condition, event) };
95
+ var matchesCondition = (condition, event, step, trackStartedAt) => {
96
+ if (condition.kind === "count") {
97
+ if (!matchesBaseEvent(condition, event))
98
+ return { matched: false };
99
+ const occurrences = (step.occurrences ?? 0) + 1;
100
+ const within = condition.withinHours === undefined || Boolean(trackStartedAt && event.occurredAt && (event.occurredAt.getTime() - trackStartedAt.getTime()) / (1000 * 60 * 60) <= condition.withinHours);
101
+ return { matched: within && occurrences >= condition.atLeast, occurrences };
102
+ }
103
+ if (condition.kind === "time_window") {
104
+ if (!matchesBaseEvent(condition, event))
105
+ return { matched: false };
106
+ if (condition.withinHoursOfStart !== undefined && trackStartedAt && event.occurredAt) {
107
+ const hoursSinceStart = (event.occurredAt.getTime() - trackStartedAt.getTime()) / (1000 * 60 * 60);
108
+ if (hoursSinceStart > condition.withinHoursOfStart) {
109
+ return { matched: false };
110
+ }
111
+ }
112
+ return { matched: true };
113
+ }
114
+ if (condition.kind === "srs_mastery") {
115
+ if (event.name !== condition.eventName)
116
+ return { matched: false };
117
+ const payload = event.payload;
118
+ if (!matchesFilter(condition.payloadFilter, payload)) {
119
+ return { matched: false };
120
+ }
121
+ const skillKey = condition.skillIdField ?? "skillId";
122
+ const masteryKey = condition.masteryField ?? "mastery";
123
+ const skillId = payload?.[skillKey];
124
+ const masteryValue = payload?.[masteryKey];
125
+ if (skillId === undefined || masteryValue === undefined) {
126
+ return { matched: false };
127
+ }
128
+ if (typeof masteryValue !== "number")
129
+ return { matched: false };
130
+ if (masteryValue < condition.minimumMastery)
131
+ return { matched: false };
132
+ const masteryCount = (step.masteryCount ?? 0) + 1;
133
+ const required = condition.requiredCount ?? 1;
134
+ return { matched: masteryCount >= required, masteryCount };
135
+ }
136
+ return { matched: matchesBaseEvent(condition, event) };
53
137
  };
54
- const getAvailability = (availability, startedAt) => {
55
- if (!availability || !startedAt) return {};
56
- const baseTime = startedAt.getTime();
57
- let unlockTime = baseTime;
58
- if (availability.unlockOnDay !== void 0) unlockTime = baseTime + (availability.unlockOnDay - 1) * 24 * 60 * 60 * 1e3;
59
- if (availability.unlockAfterHours !== void 0) unlockTime = baseTime + availability.unlockAfterHours * 60 * 60 * 1e3;
60
- const availableAt = new Date(unlockTime);
61
- return {
62
- availableAt,
63
- dueAt: availability.dueWithinHours !== void 0 ? new Date(availableAt.getTime() + availability.dueWithinHours * 60 * 60 * 1e3) : void 0
64
- };
138
+ var getAvailability = (availability, startedAt) => {
139
+ if (!availability || !startedAt)
140
+ return {};
141
+ const baseTime = startedAt.getTime();
142
+ let unlockTime = baseTime;
143
+ if (availability.unlockOnDay !== undefined) {
144
+ unlockTime = baseTime + (availability.unlockOnDay - 1) * 24 * 60 * 60 * 1000;
145
+ }
146
+ if (availability.unlockAfterHours !== undefined) {
147
+ unlockTime = baseTime + availability.unlockAfterHours * 60 * 60 * 1000;
148
+ }
149
+ const availableAt = new Date(unlockTime);
150
+ const dueAt = availability.dueWithinHours !== undefined ? new Date(availableAt.getTime() + availability.dueWithinHours * 60 * 60 * 1000) : undefined;
151
+ return { availableAt, dueAt };
65
152
  };
66
- const computeProgressPercent = (steps) => {
67
- const total = steps.length || 1;
68
- const done = steps.filter((s) => s.status === "COMPLETED").length;
69
- return Math.round(done / total * 100);
153
+ var computeProgressPercent = (steps) => {
154
+ const total = steps.length || 1;
155
+ const done = steps.filter((s) => s.status === "COMPLETED").length;
156
+ return Math.round(done / total * 100);
70
157
  };
71
- const applyTrackCompletionBonuses = (track, progress) => {
72
- if (progress.isCompleted) return progress;
73
- const completedAt = /* @__PURE__ */ new Date();
74
- const startedAt = progress.startedAt ?? completedAt;
75
- const hoursElapsed = (completedAt.getTime() - startedAt.getTime()) / (1e3 * 60 * 60);
76
- let xpEarned = progress.xpEarned;
77
- const { completionRewards, streakRule } = track;
78
- if (completionRewards?.xpBonus) xpEarned += completionRewards.xpBonus;
79
- if (streakRule?.hoursWindow !== void 0 && hoursElapsed <= streakRule.hoursWindow && streakRule.bonusXp) xpEarned += streakRule.bonusXp;
80
- return {
81
- ...progress,
82
- xpEarned,
83
- isCompleted: true,
84
- completedAt,
85
- lastActivityAt: completedAt
86
- };
158
+ var applyTrackCompletionBonuses = (track, progress) => {
159
+ if (progress.isCompleted)
160
+ return progress;
161
+ const completedAt = new Date;
162
+ const startedAt = progress.startedAt ?? completedAt;
163
+ const hoursElapsed = (completedAt.getTime() - startedAt.getTime()) / (1000 * 60 * 60);
164
+ let xpEarned = progress.xpEarned;
165
+ const { completionRewards, streakRule } = track;
166
+ if (completionRewards?.xpBonus) {
167
+ xpEarned += completionRewards.xpBonus;
168
+ }
169
+ if (streakRule?.hoursWindow !== undefined && hoursElapsed <= streakRule.hoursWindow && streakRule.bonusXp) {
170
+ xpEarned += streakRule.bonusXp;
171
+ }
172
+ return {
173
+ ...progress,
174
+ xpEarned,
175
+ isCompleted: true,
176
+ completedAt,
177
+ lastActivityAt: completedAt
178
+ };
87
179
  };
88
- const listTracks = (learnerId) => {
89
- const progressMap = learnerId ? getLearnerTracks(learnerId) : void 0;
90
- return {
91
- tracks: learningJourneyTracks,
92
- progress: learnerId && progressMap ? Array.from(progressMap.values()) : []
93
- };
180
+ var listTracks = (learnerId) => {
181
+ const progressMap = learnerId ? getLearnerTracks(learnerId) : undefined;
182
+ const progress = learnerId && progressMap ? Array.from(progressMap.values()) : [];
183
+ return {
184
+ tracks: learningJourneyTracks,
185
+ progress
186
+ };
94
187
  };
95
- const getProgress = (trackId, learnerId) => {
96
- const track = getTrack(trackId);
97
- if (!track) return void 0;
98
- const map = getLearnerTracks(learnerId);
99
- const existing = map.get(trackId) ?? initProgress(learnerId, track);
100
- map.set(trackId, existing);
101
- return existing;
188
+ var getProgress = (trackId, learnerId) => {
189
+ const track = getTrack(trackId);
190
+ if (!track)
191
+ return;
192
+ const map = getLearnerTracks(learnerId);
193
+ const existing = map.get(trackId) ?? initProgress(learnerId, track);
194
+ map.set(trackId, existing);
195
+ return existing;
102
196
  };
103
- const recordEvent = (event) => {
104
- const targets = event.trackId !== void 0 ? learningJourneyTracks.filter((t) => t.id === event.trackId) : learningJourneyTracks;
105
- const updated = [];
106
- const eventTime = event.occurredAt ?? /* @__PURE__ */ new Date();
107
- for (const track of targets) {
108
- const map = getLearnerTracks(event.learnerId);
109
- const current = map.get(track.id) ?? initProgress(event.learnerId, track);
110
- const startedAt = current.startedAt ?? eventTime;
111
- let changed = current.startedAt === void 0;
112
- const steps = current.steps.map((step) => {
113
- if (step.status === "COMPLETED") return step;
114
- const spec = track.steps.find((s) => s.id === step.id);
115
- if (!spec) return step;
116
- const { availableAt, dueAt } = getAvailability(spec.availability, startedAt);
117
- if (availableAt && eventTime < availableAt) return {
118
- ...step,
119
- availableAt,
120
- dueAt
121
- };
122
- if (dueAt && eventTime > dueAt) return {
123
- ...step,
124
- availableAt,
125
- dueAt
126
- };
127
- const result = matchesCondition(spec.completion, event, step, startedAt);
128
- if (result.matched) {
129
- changed = true;
130
- return {
131
- ...step,
132
- status: "COMPLETED",
133
- xpEarned: spec.xpReward ?? 0,
134
- completedAt: eventTime,
135
- triggeringEvent: event.name,
136
- eventPayload: event.payload,
137
- occurrences: result.occurrences ?? step.occurrences,
138
- masteryCount: result.masteryCount ?? step.masteryCount,
139
- availableAt,
140
- dueAt
141
- };
142
- }
143
- if (result.occurrences !== void 0 || result.masteryCount !== void 0) changed = true;
144
- return {
145
- ...step,
146
- occurrences: result.occurrences ?? step.occurrences,
147
- masteryCount: result.masteryCount ?? step.masteryCount,
148
- availableAt,
149
- dueAt
150
- };
151
- });
152
- if (!changed) continue;
153
- const xpEarned = steps.reduce((sum, s) => sum + s.xpEarned, 0) + (track.totalXp ?? 0);
154
- let progress = {
155
- ...current,
156
- steps,
157
- xpEarned,
158
- startedAt,
159
- lastActivityAt: eventTime,
160
- progress: computeProgressPercent(steps)
161
- };
162
- if (steps.every((s) => s.status === "COMPLETED")) progress = applyTrackCompletionBonuses(track, progress);
163
- map.set(track.id, progress);
164
- updated.push(progress);
165
- }
166
- return updated;
197
+ var recordEvent = (event) => {
198
+ const targets = event.trackId !== undefined ? learningJourneyTracks.filter((t) => t.id === event.trackId) : learningJourneyTracks;
199
+ const updated = [];
200
+ const eventTime = event.occurredAt ?? new Date;
201
+ for (const track of targets) {
202
+ const map = getLearnerTracks(event.learnerId);
203
+ const current = map.get(track.id) ?? initProgress(event.learnerId, track);
204
+ const startedAt = current.startedAt ?? eventTime;
205
+ let changed = current.startedAt === undefined;
206
+ const steps = current.steps.map((step) => {
207
+ if (step.status === "COMPLETED")
208
+ return step;
209
+ const spec = track.steps.find((s) => s.id === step.id);
210
+ if (!spec)
211
+ return step;
212
+ const { availableAt, dueAt } = getAvailability(spec.availability, startedAt);
213
+ if (availableAt && eventTime < availableAt) {
214
+ return { ...step, availableAt, dueAt };
215
+ }
216
+ if (dueAt && eventTime > dueAt) {
217
+ return { ...step, availableAt, dueAt };
218
+ }
219
+ const result = matchesCondition(spec.completion, event, step, startedAt);
220
+ if (result.matched) {
221
+ changed = true;
222
+ return {
223
+ ...step,
224
+ status: "COMPLETED",
225
+ xpEarned: spec.xpReward ?? 0,
226
+ completedAt: eventTime,
227
+ triggeringEvent: event.name,
228
+ eventPayload: event.payload,
229
+ occurrences: result.occurrences ?? step.occurrences,
230
+ masteryCount: result.masteryCount ?? step.masteryCount,
231
+ availableAt,
232
+ dueAt
233
+ };
234
+ }
235
+ if (result.occurrences !== undefined || result.masteryCount !== undefined) {
236
+ changed = true;
237
+ }
238
+ return {
239
+ ...step,
240
+ occurrences: result.occurrences ?? step.occurrences,
241
+ masteryCount: result.masteryCount ?? step.masteryCount,
242
+ availableAt,
243
+ dueAt
244
+ };
245
+ });
246
+ if (!changed) {
247
+ continue;
248
+ }
249
+ const xpEarned = steps.reduce((sum, s) => sum + s.xpEarned, 0) + (track.totalXp ?? 0);
250
+ let progress = {
251
+ ...current,
252
+ steps,
253
+ xpEarned,
254
+ startedAt,
255
+ lastActivityAt: eventTime,
256
+ progress: computeProgressPercent(steps)
257
+ };
258
+ const allDone = steps.every((s) => s.status === "COMPLETED");
259
+ if (allDone) {
260
+ progress = applyTrackCompletionBonuses(track, progress);
261
+ }
262
+ map.set(track.id, progress);
263
+ updated.push(progress);
264
+ }
265
+ return updated;
266
+ };
267
+ export {
268
+ recordEvent,
269
+ listTracks,
270
+ getProgress
167
271
  };
168
-
169
- //#endregion
170
- export { getProgress, listTracks, recordEvent };
171
- //# sourceMappingURL=api.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=api.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.test.d.ts","sourceRoot":"","sources":["../src/api.test.ts"],"names":[],"mappings":""}
File without changes