musora-content-services 2.136.2 → 2.136.4

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.
@@ -5,9 +5,9 @@ name: Node.js CI
5
5
 
6
6
  on:
7
7
  push:
8
- branches: [ "main" ]
8
+ branches: [ ]
9
9
  pull_request:
10
- branches: [ "main" ]
10
+ branches: [ ]
11
11
 
12
12
  jobs:
13
13
  build:
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
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.136.4](https://github.com/railroadmedia/musora-content-services/compare/v2.136.3...v2.136.4) (2026-02-20)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * better Sentry logging ([#832](https://github.com/railroadmedia/musora-content-services/issues/832)) ([73bd384](https://github.com/railroadmedia/musora-content-services/commit/73bd38473acb631a9f30d559e403e8f4203bb548))
11
+
12
+ ### [2.136.3](https://github.com/railroadmedia/musora-content-services/compare/v2.136.2...v2.136.3) (2026-02-20)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * change query ([#831](https://github.com/railroadmedia/musora-content-services/issues/831)) ([8e7d2a2](https://github.com/railroadmedia/musora-content-services/commit/8e7d2a20cecc6bc134664e72b0c30cc067ba0591))
18
+
5
19
  ### [2.136.2](https://github.com/railroadmedia/musora-content-services/compare/v2.136.1...v2.136.2) (2026-02-19)
6
20
 
7
21
  ### [2.136.1](https://github.com/railroadmedia/musora-content-services/compare/v2.136.0...v2.136.1) (2026-02-19)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.136.2",
3
+ "version": "2.136.4",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -2003,10 +2003,17 @@ export async function fetchScheduledAndNewReleases(
2003
2003
  const sortOrder = getSortOrder(sort, brand)
2004
2004
 
2005
2005
  const query = `
2006
- *[(_type in [${typesString}] && brand == '${brand}' && ((status in ['published','scheduled'] )||(show_in_new_feed == true)))
2007
- || (defined(live_event_start_time) && (!defined(live_event_end_time) || live_event_end_time >= '${now}' ) && (brand == '${brand}' || brand == 'musora' && live_global_event) && status in ['scheduled'])]
2006
+ *[show_in_new_feed == true && (
2007
+ (_type in [${typesString}] && brand == '${brand}' && status in ['published','scheduled'])
2008
+ || (
2009
+ defined(live_event_start_time)
2010
+ && (!defined(live_event_end_time) || live_event_end_time >= '${now}' )
2011
+ && brand == '${brand}'
2012
+ && status in ['scheduled']
2013
+ )
2014
+ )]
2008
2015
  [${start}...${end}]
2009
- | order(published_on asc) {
2016
+ | order(${sortOrder}) {
2010
2017
  "id": railcontent_id,
2011
2018
  title,
2012
2019
  "image": thumbnail.asset->url,
@@ -19,14 +19,42 @@ export default class PracticesRepository extends SyncRepository<Practice> {
19
19
  }
20
20
 
21
21
  async trackAutoPractice(contentId: number, date: string, incrementalDurationSeconds: number, skipPush = true) {
22
- return await this.upsertOne(Practice.generateAutoId(contentId, date), r => {
22
+ this.store.telemetry.log('trackUserPractice:before', {
23
+ now: performance.now(),
24
+ contentId,
25
+ date,
26
+ incrementalDurationSeconds
27
+ })
28
+
29
+ const result = await this.upsertOne(Practice.generateAutoId(contentId, date), r => {
30
+ const oldDurationSeconds = r.duration_seconds;
31
+
23
32
  r._raw.id = Practice.generateAutoId(contentId, date);
24
33
  r.auto = true;
25
34
  r.content_id = contentId;
26
35
  r.date = date;
27
36
 
28
37
  r.duration_seconds = Math.min((r.duration_seconds || 0) + incrementalDurationSeconds, 59999);
38
+
39
+ this.store.telemetry.log('trackUserPractice:around', {
40
+ now: performance.now(),
41
+ contentId,
42
+ date,
43
+ incrementalDurationSeconds,
44
+ before: oldDurationSeconds,
45
+ after: r.duration_seconds
46
+ })
29
47
  }, { skipPush })
48
+
49
+ this.store.telemetry.log('trackUserPractice:after', {
50
+ now: performance.now(),
51
+ contentId,
52
+ date,
53
+ incrementalDurationSeconds,
54
+ after: result.data.duration_seconds
55
+ })
56
+
57
+ return result
30
58
  }
31
59
 
32
60
  async recordManualPractice(date: string, durationSeconds: number, details: Partial<Pick<Practice, 'title' | 'instrument_id' | 'category_id' | 'thumbnail_url'>> = {}) {
@@ -49,7 +77,7 @@ export default class PracticesRepository extends SyncRepository<Practice> {
49
77
 
50
78
  async updateDetails(id: RecordId, details: Partial<Pick<Practice, 'duration_seconds' | 'title' | 'thumbnail_url' | 'category_id' | 'instrument_id'>>) {
51
79
  return await this.updateOneId(id, r => {
52
- r.duration_seconds = Math.min(details.duration_seconds, 59999) ?? r.duration_seconds;
80
+ r.duration_seconds = details.duration_seconds !== null ? Math.min(details.duration_seconds, 59999) : r.duration_seconds;
53
81
  r.title = details.title ?? r.title;
54
82
  r.thumbnail_url = details.thumbnail_url ?? r.thumbnail_url;
55
83
  r.category_id = details.category_id ?? r.category_id;
@@ -667,7 +667,7 @@ export default class SyncStore<TModel extends BaseModel = BaseModel> {
667
667
  if (failedResults.length) {
668
668
  this.telemetry.warn(
669
669
  `[store:${this.model.table}] Push completed with failed records`,
670
- { results: failedResults, resultsJSON: JSON.stringify(failedResults) }
670
+ { extra: { resultsJSON: JSON.stringify(failedResults) } }
671
671
  )
672
672
  }
673
673
 
@@ -10,12 +10,14 @@ export type SentryLike = {
10
10
  captureMessage: typeof InjectedSentry.captureMessage
11
11
  addBreadcrumb: typeof InjectedSentry.addBreadcrumb
12
12
  startSpan: typeof InjectedSentry.startSpan
13
+ logger: typeof InjectedSentry.logger
13
14
  }
14
15
 
15
16
  export type StartSpanOptions = Parameters<typeof InjectedSentry.startSpan>[0]
16
17
  export type Span = InjectedSentry.Span
17
18
 
18
19
  export const SYNC_TELEMETRY_TRACE_PREFIX = 'sync:'
20
+ export const SYNC_TELEMETRY_CONSOLE_PREFIX = '📡 SYNC:'
19
21
 
20
22
  export enum SeverityLevel {
21
23
  DEBUG = 0,
@@ -35,6 +37,13 @@ const severityLevelToSentryLevel: Record<SeverityLevel, 'fatal' | 'error' | 'war
35
37
  [SeverityLevel.FATAL]: 'fatal',
36
38
  }
37
39
 
40
+ type CaptureOptions = {
41
+ tags?: Record<string, string>
42
+ contexts?: Record<string, Record<string, unknown>>
43
+ extra?: unknown
44
+ }
45
+ type LogAttributes = Record<string, unknown>
46
+
38
47
  export class SyncTelemetry {
39
48
  private static instance: SyncTelemetry | null = null
40
49
 
@@ -58,9 +67,6 @@ export class SyncTelemetry {
58
67
 
59
68
  private ignorePatterns: (string | RegExp)[] = []
60
69
 
61
- // allows us to know if Sentry shouldn't double-capture a dev-prettified console.error log
62
- private _ignoreConsole = false
63
-
64
70
  constructor(
65
71
  userScope: SyncUserScope,
66
72
  {
@@ -81,7 +87,7 @@ export class SyncTelemetry {
81
87
  this.level = typeof normalizedLevel === 'number' ? normalizedLevel : SeverityLevel.LOG
82
88
  this.pretty = typeof pretty !== 'undefined' ? pretty : true
83
89
 
84
- watermelonLogger.log = (message: unknown) => this.log(message instanceof Error ? message : ['[Watermelon]', message].join(' '))
90
+ watermelonLogger.log = (message: unknown) => this.log(message instanceof Error ? message.message : ['[Watermelon]', message].join(' '))
85
91
  watermelonLogger.warn = (message: unknown) => this.warn(message instanceof Error ? message : ['[Watermelon]', message].join(' '))
86
92
  watermelonLogger.error = (message: unknown) => this.error(message instanceof Error ? message : ['[Watermelon]', message].join(' '))
87
93
  }
@@ -100,9 +106,9 @@ export class SyncTelemetry {
100
106
  let desc = span['_spanId'].slice(0, 4)
101
107
  desc += span['_parentSpanId'] ? ` (< ${span['_parentSpanId'].slice(0, 4)})` : ''
102
108
 
103
- this._log(SeverityLevel.DEBUG, 'info', `[trace:start] ${options.name} (${desc})`, true)
109
+ this.consoleLog(SeverityLevel.DEBUG, 'info', `[trace:start] ${options.name} (${desc})`)
104
110
  const result = callback(span)
105
- Promise.resolve(result).finally(() => this._log(SeverityLevel.DEBUG, 'info', `[trace:end] ${options.name} (${desc})`, true))
111
+ Promise.resolve(result).finally(() => this.consoleLog(SeverityLevel.DEBUG, 'info', `[trace:end] ${options.name} (${desc})`))
106
112
 
107
113
  return result
108
114
  })
@@ -124,88 +130,79 @@ export class SyncTelemetry {
124
130
  : undefined
125
131
  )
126
132
 
127
- this._ignoreConsole = true
128
- this.error(err instanceof Error ? err.message : String(err))
129
- this._ignoreConsole = false
133
+ this.consoleLog(SeverityLevel.ERROR, 'error', err)
130
134
  }
131
135
 
132
- // allows us to know if Sentry shouldn't double-capture a dev-prettified console.error log
133
- shouldIgnoreConsole() {
134
- return this._ignoreConsole
136
+ debug(message: string, attrs?: LogAttributes) {
137
+ this.logMessage(SeverityLevel.DEBUG, 'info', message, attrs)
135
138
  }
136
139
 
137
- /**
138
- * Ignore messages/errors in the future that match provided patterns
139
- */
140
- ignoreLike(...patterns: (RegExp | string)[]) {
141
- this.ignorePatterns.push(...patterns)
140
+ info(message: string, attrs?: LogAttributes) {
141
+ this.logMessage(SeverityLevel.INFO, 'info', message, attrs)
142
142
  }
143
143
 
144
- shouldIgnoreRejection(reason: any) {
145
- const message = reason instanceof Error ? `${reason.name}: ${reason.message}` : reason
146
- return this.shouldIgnoreMessage(message)
144
+ log(message: string, attrs?: LogAttributes) {
145
+ this.logMessage(SeverityLevel.LOG, 'log', message, attrs)
147
146
  }
148
147
 
149
- shouldIgnoreException(exception: unknown) {
150
- if (exception instanceof Error) {
151
- return this.shouldIgnoreMessage(exception.message)
152
- }
153
-
154
- return false
148
+ warn(message: unknown, opts?: CaptureOptions) {
149
+ this.captureError(SeverityLevel.WARNING, 'warn', message, opts)
155
150
  }
156
151
 
157
- debug(message: unknown, extra?: any) {
158
- this._log(SeverityLevel.DEBUG, 'info', message, extra)
152
+ error(message: unknown, opts?: CaptureOptions) {
153
+ this.captureError(SeverityLevel.ERROR, 'error', message, opts)
159
154
  }
160
155
 
161
- info(message: unknown, extra?: any) {
162
- this._log(SeverityLevel.INFO, 'info', message, extra)
156
+ fatal(message: unknown, opts?: CaptureOptions) {
157
+ this.captureError(SeverityLevel.FATAL, 'error', message, opts)
163
158
  }
164
159
 
165
- log(message: unknown, extra?: any) {
166
- this._log(SeverityLevel.LOG, 'log', message, extra)
167
- }
160
+ private logMessage(level: SeverityLevel, consoleMethod: 'info' | 'log', message: string, attrs?: LogAttributes) {
161
+ if (this.level > level) return
162
+ if (this.shouldIgnoreMessage(message)) return
168
163
 
169
- warn(message: unknown, extra?: any) {
170
- this._log(SeverityLevel.WARNING, 'warn', message, extra)
164
+ console[consoleMethod](...this.consoleFormattedMessage(message, attrs))
165
+ const sentryLevel = severityLevelToSentryLevel[level]
166
+ const sentryLogMethod = sentryLevel === 'debug' ? 'debug' : 'info'
167
+ this.Sentry.addBreadcrumb({
168
+ message,
169
+ data: attrs,
170
+ level: sentryLevel,
171
+ })
172
+ this.Sentry.logger[sentryLogMethod](message, attrs)
171
173
  }
172
174
 
173
- error(message: unknown, extra?: any) {
174
- this._log(SeverityLevel.ERROR, 'error', message, extra)
175
- }
175
+ private captureError(level: SeverityLevel, consoleMethod: 'warn' | 'error', error: unknown, opts: CaptureOptions) {
176
+ if (this.level > level) return
177
+ if (error instanceof Error && this.shouldIgnoreMessage(error.message)) return
178
+ if (typeof error === 'string' && this.shouldIgnoreMessage(error)) return
176
179
 
177
- fatal(message: unknown, extra?: any) {
178
- this._log(SeverityLevel.FATAL, 'error', message, extra)
180
+ console[consoleMethod](...this.consoleFormattedMessage(error, opts))
181
+ this.Sentry.captureException(error, { level: severityLevelToSentryLevel[level] })
179
182
  }
180
183
 
181
- _log(level: SeverityLevel, consoleMethod: 'info' | 'log' | 'warn' | 'error', message: unknown, extra?: any, skipSentry = false) {
184
+ private consoleLog(level: SeverityLevel, consoleMethod: 'info' | 'log' | 'warn' | 'error', message: unknown, extra?: any) {
182
185
  if (this.level > level || this.shouldIgnoreMessage(message)) return
183
- this._ignoreConsole = true
184
- console[consoleMethod](...this.formattedConsoleMessage(message, extra))
185
- this._ignoreConsole = false
186
-
187
- if (skipSentry) return;
186
+ console[consoleMethod](...this.consoleFormattedMessage(message, extra))
187
+ }
188
188
 
189
- if (level >= SeverityLevel.WARNING) {
190
- this.Sentry.captureMessage(message instanceof Error ? message.message : String(message), severityLevelToSentryLevel[level])
191
- } else {
192
- this.Sentry.addBreadcrumb({ message: message instanceof Error ? message.message : String(message), level: severityLevelToSentryLevel[level] })
193
- }
189
+ static isSyncConsoleMessage(args: unknown[]): boolean {
190
+ return typeof args[0] === 'string' && args[0].startsWith(SYNC_TELEMETRY_CONSOLE_PREFIX)
194
191
  }
195
192
 
196
- private formattedConsoleMessage(message: unknown, extra: any) {
193
+ private consoleFormattedMessage(message: unknown, remnant: CaptureOptions | LogAttributes) {
197
194
  if (!this.pretty) {
198
- return [message, ...(extra ? [extra] : [])]
195
+ return [message, ...(remnant ? [remnant] : [])]
199
196
  }
200
197
 
201
198
  const date = new Date()
202
- return [...this.consolePrefix(date), message, ...(extra ? [extra] : []), ...this.consoleSuffix(date)]
199
+ return [...this.consolePrefix(date), message, ...(remnant ? [remnant] : []), ...this.consoleSuffix(date)]
203
200
  }
204
201
 
205
202
  private consolePrefix(date: Date) {
206
203
  const now = Math.round(date.getTime() / 1000).toString()
207
204
  return [
208
- `📡 SYNC: (%c${now.slice(0, 5)}%c${now.slice(5, 10)})`,
205
+ `${SYNC_TELEMETRY_CONSOLE_PREFIX} (%c${now.slice(0, 5)}%c${now.slice(5, 10)})`,
209
206
  'color: #ccc',
210
207
  'font-weight: bold;',
211
208
  ]
@@ -215,6 +212,23 @@ export class SyncTelemetry {
215
212
  return [` [${date.toLocaleTimeString()}, ${date.getTime()}]`]
216
213
  }
217
214
 
215
+ ignoreLike(...patterns: (RegExp | string)[]) {
216
+ this.ignorePatterns.push(...patterns)
217
+ }
218
+
219
+ shouldIgnoreRejection(reason: any) {
220
+ const message = reason instanceof Error ? `${reason.name}: ${reason.message}` : reason
221
+ return this.shouldIgnoreMessage(message)
222
+ }
223
+
224
+ shouldIgnoreException(exception: unknown) {
225
+ if (exception instanceof Error) {
226
+ return this.shouldIgnoreMessage(exception.message)
227
+ }
228
+
229
+ return false
230
+ }
231
+
218
232
  private shouldIgnoreMessage(message: any) {
219
233
  if (message instanceof Error) message = message.message
220
234
  if (typeof message !== 'string') return false
@@ -6,7 +6,8 @@ type BeforeSendResult<T> = T | PromiseLike<T | null> | null | undefined
6
6
  type SamplerResult = number | boolean | undefined
7
7
 
8
8
  export const syncSentryBeforeSend = (event: ErrorEvent, hint: EventHint): BeforeSendResult<ErrorEvent> => {
9
- if (event.logger === 'console' && SyncTelemetry.getInstance()?.shouldIgnoreConsole()) {
9
+ const consoleArgs = event.extra?.arguments as unknown[] | undefined
10
+ if (event.logger === 'console' && consoleArgs && SyncTelemetry.isSyncConsoleMessage(consoleArgs)) {
10
11
  return null
11
12
  }
12
13