musora-content-services 2.180.0 → 2.181.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.
@@ -1,50 +1,16 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(npx jest *)",
5
- "Bash(npx tsc *)",
6
- "Skill(counselors)",
7
- "Bash(counselors ls *)",
8
- "Bash(counselors groups *)",
9
- "Bash(counselors run *)",
4
+ "Bash(rg:*)",
5
+ "Bash(npm run lint:*)",
6
+ "Bash(ls:*)",
10
7
  "Bash(npm test *)",
11
- "Bash(gh pr *)",
12
- "Bash(gh api *)",
13
- "Bash(mkdir -p /tmp/pr-review-v2)",
14
- "Read(//tmp/pr-review-v2/**)",
15
- "Bash(cat /home/alesevero/railenvironment/applications/musora-content-services/AGENTS.md)",
16
- "Bash(echo \"no AGENTS.md\")",
17
- "Bash(echo \"exit=$?\")",
18
- "Bash(git checkout *)",
19
- "Skill(pr)",
20
- "Skill(create-decision)",
21
- "mcp__github__pull_request_read",
22
- "mcp__github__push_files",
23
- "Bash(git push *)",
24
- "mcp__github__create_pull_request",
25
- "mcp__github__search_pull_requests",
26
- "Bash(git commit *)",
27
- "Bash(git add *)",
28
- "mcp__github__list_pull_requests",
29
- "mcp__sanity__get_schema",
30
- "mcp__sanity__query_documents",
31
- "Bash(git rm *)",
32
- "Bash(git rebase *)",
33
- "Bash(git fetch *)",
34
- "Bash(git --no-pager log --oneline -1)",
35
- "Bash(git --no-pager log --oneline -5)",
36
- "Bash(git ls-tree *)",
37
- "Bash(git --no-pager log --oneline -2)",
38
- "Bash(git --no-pager diff --stat HEAD~1)",
39
- "Bash(git check-ignore *)",
40
- "Bash(python3 -)",
41
- "Bash(git --no-pager log --oneline -3)",
42
- "Bash(mkdir *)",
43
- "Bash(git --no-pager log --oneline origin/main..HEAD)"
44
- ]
8
+ "Bash(npx jest *)"
9
+ ],
10
+ "deny": []
45
11
  },
46
- "model": "sonnet",
47
12
  "enabledMcpjsonServers": [
13
+ "1password",
48
14
  "atlassian",
49
15
  "figma",
50
16
  "google-workspace",
@@ -58,14 +24,10 @@
58
24
  "chrome-devtools",
59
25
  "railway",
60
26
  "github",
61
- "asana"
62
- ],
63
- "disabledMcpjsonServers": [
27
+ "asana",
64
28
  "nightwatch",
65
- "1password",
66
29
  "mysql-staging",
67
30
  "mysql-local",
68
31
  "mobile"
69
- ],
70
- "effortLevel": "medium"
32
+ ]
71
33
  }
Binary file
package/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [2.181.0](https://github.com/railroadmedia/musora-content-services/compare/v2.180.0...v2.181.0) (2026-09-17)
6
+
7
+
8
+ ### Features
9
+
10
+ * allow merged config ([#1018](https://github.com/railroadmedia/musora-content-services/issues/1018)) ([8d8a9aa](https://github.com/railroadmedia/musora-content-services/commit/8d8a9aaed048e83b7dfeb15a7c12fabd199721f1))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **BR-717:** expire streak cache at calendar day boundary ([1f4481c](https://github.com/railroadmedia/musora-content-services/commit/1f4481cc5a71afa990c39d5927099b093a475c83))
16
+ * **BR-964:** unpin deleted playlists at fetch time ([#1052](https://github.com/railroadmedia/musora-content-services/issues/1052)) ([0367143](https://github.com/railroadmedia/musora-content-services/commit/036714377469beccbceae93d138a9d2280516395))
17
+
5
18
  ## [2.180.0](https://github.com/railroadmedia/musora-content-services/compare/v2.179.0...v2.180.0) (2026-09-04)
6
19
 
7
20
 
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.180.0",
3
+ "version": "2.181.0",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.d.ts CHANGED
@@ -20,7 +20,8 @@ import {
20
20
  getMCSVersion,
21
21
  globalConfig,
22
22
  initializeEnvVar,
23
- initializeService
23
+ initializeService,
24
+ updateSessionConfig
24
25
  } from './services/config.js';
25
26
 
26
27
  import {
@@ -917,6 +918,7 @@ declare module 'musora-content-services' {
917
918
  updatePracticeGoals,
918
919
  updatePracticeNotes,
919
920
  updateProfileVisibility,
921
+ updateSessionConfig,
920
922
  updateThread,
921
923
  updateUserPractice,
922
924
  upgradeSubscription,
package/src/index.js CHANGED
@@ -24,7 +24,8 @@ import {
24
24
  getMCSVersion,
25
25
  globalConfig,
26
26
  initializeEnvVar,
27
- initializeService
27
+ initializeService,
28
+ updateSessionConfig
28
29
  } from './services/config.js';
29
30
 
30
31
  import {
@@ -916,6 +917,7 @@ export {
916
917
  updatePracticeGoals,
917
918
  updatePracticeNotes,
918
919
  updateProfileVisibility,
920
+ updateSessionConfig,
919
921
  updateThread,
920
922
  updateUserPractice,
921
923
  upgradeSubscription,
@@ -23,9 +23,28 @@ export let globalConfig = {
23
23
  */
24
24
  const excludeFromGeneratedIndex = []
25
25
 
26
+ /**
27
+ * @param {*} target
28
+ * @param {*} source
29
+ * @returns {*}
30
+ */
31
+ function deepMerge(target, source) {
32
+ if (source === undefined) return target
33
+ if (typeof source !== 'object' || source === null || Array.isArray(source)) return source
34
+
35
+ const merged = { ...target }
36
+ for (const key of Object.keys(source)) {
37
+ merged[key] = deepMerge(target?.[key], source[key])
38
+ }
39
+ return merged
40
+ }
41
+
26
42
  /**
27
43
  * Initializes the service with the given configuration.
28
44
  * This function must be called before using any other functions in this library.
45
+ * Each call replaces the prior config wholesale - pass the full config every time.
46
+ * For a partial update to just the session (e.g. userId resolving after boot),
47
+ * use {@link updateSessionConfig} instead.
29
48
  * Automatically initializes award definitions with 24-hour cache in the background.
30
49
  *
31
50
  * @param {Config} config - Configuration object containing API settings.
@@ -107,6 +126,18 @@ export function initializeService(config) {
107
126
  }
108
127
  }
109
128
 
129
+ /**
130
+ * Deep-merges partial session data (e.g. userId) into the existing sessionConfig
131
+ * without touching any other part of globalConfig. Use this for reactive updates
132
+ * (e.g. a watcher that re-syncs userId once a user resolves) instead of calling
133
+ * initializeService again, since initializeService replaces sessionConfig wholesale.
134
+ *
135
+ * @param {*} sessionConfig
136
+ */
137
+ export function updateSessionConfig(sessionConfig) {
138
+ globalConfig.sessionConfig = deepMerge(globalConfig.sessionConfig, sessionConfig)
139
+ }
140
+
110
141
  export function initializeEnvVar(config) {
111
142
  globalConfig.appEnv = config.appEnv
112
143
  }
@@ -22,7 +22,7 @@ const CACHE_EXPIRY_MS = 5 * 60 * 1000
22
22
  /**
23
23
  * Retrieves user's pinned data by brand, from localStorage or BE call.
24
24
  * @param brand
25
- * @returns {Promise<any|*|{id, type}>}
25
+ * @returns {Promise<any|*|{id, type}>} - only id and type
26
26
  */
27
27
  async function getUserPinnedItem(brand) {
28
28
  const key = getUserPinProgressKey()
@@ -170,7 +170,12 @@ export async function getProgressRows({ brand = 'drumeo', limit = 8 } = {}, opti
170
170
 
171
171
  const [contentCardMap, playlistCards, methodCard] = await getCards(brand, limit, userPinnedItem)
172
172
 
173
- const pinnedCard = await popPinnedItem(userPinnedItem, contentCardMap, playlistCards, methodCard)
173
+ let pinnedCard = null
174
+ try {
175
+ pinnedCard = await popPinnedItem(userPinnedItem, contentCardMap, playlistCards, methodCard, brand) // todo: catch errors in here
176
+ } catch (error) {
177
+ console.error('Error popping pinned item:', error)
178
+ }
174
179
 
175
180
  let allResultsLength = playlistCards.length + contentCardMap.size
176
181
  if (methodCard) {
@@ -207,7 +212,7 @@ async function getCards(brand, limit, userPinnedItem) {
207
212
  * If userPinnedItem is not found, generate the pinned card from scratch.
208
213
  *
209
214
  **/
210
- async function popPinnedItem(userPinnedItem, contentCardMap, playlistCards, methodCard) {
215
+ async function popPinnedItem(userPinnedItem, contentCardMap, playlistCards, methodCard, brand) {
211
216
  if (!userPinnedItem) return null
212
217
  const pinnedId = parseInt(userPinnedItem.id)
213
218
  const progressType = userPinnedItem.progressType ?? userPinnedItem.type
@@ -236,13 +241,24 @@ async function popPinnedItem(userPinnedItem, contentCardMap, playlistCards, meth
236
241
  if (pinnedPlaylist) {
237
242
  item = pinnedPlaylist
238
243
  } else {
239
- const playlist = await fetchPlaylist(pinnedId)
240
- item = processPlaylistItem({
241
- id: pinnedId,
242
- playlist: playlist,
243
- type: 'playlist',
244
- progressTimestamp: new Date().getTime(),
245
- })
244
+ let playlist
245
+ try {
246
+ playlist = await fetchPlaylist(pinnedId)
247
+ } catch (error) {
248
+ if (error?.status !== 404) {
249
+ throw error
250
+ }
251
+ unpinProgressRow(brand)
252
+ }
253
+
254
+ if (playlist) {
255
+ item = processPlaylistItem({
256
+ id: pinnedId,
257
+ playlist: playlist,
258
+ type: 'playlist',
259
+ progressTimestamp: new Date().getTime(),
260
+ })
261
+ }
246
262
  }
247
263
  } else if (progressType === 'method') {
248
264
  // simply get method card and return
@@ -22,13 +22,17 @@ export interface PracticeData {
22
22
  class StreakCalculator {
23
23
  private cache: StreakData | null = null
24
24
  async getStreakData(): Promise<StreakData> {
25
- if (this.cache) {
25
+ if (this.cache && this.isCacheFromToday()) {
26
26
  return this.cache
27
27
  }
28
28
 
29
29
  return await this.recalculate()
30
30
  }
31
31
 
32
+ private isCacheFromToday(): boolean {
33
+ return dayjs(this.cache.calculatedAt).format('YYYY-MM-DD') === dayjs().format('YYYY-MM-DD')
34
+ }
35
+
32
36
  async recalculate(): Promise<StreakData> {
33
37
  const allPractices = await this.fetchAllPractices()
34
38
 
@@ -1 +1 @@
1
- export const MCS_VERSION = '2.180.0'
1
+ export const MCS_VERSION = '2.181.0'