musora-content-services 2.111.3 → 2.111.5

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/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
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.111.5](https://github.com/railroadmedia/musora-content-services/compare/v2.111.4...v2.111.5) (2026-01-07)
6
+
7
+ ### [2.111.4](https://github.com/railroadmedia/musora-content-services/compare/v2.111.3...v2.111.4) (2026-01-07)
8
+
5
9
  ### [2.111.3](https://github.com/railroadmedia/musora-content-services/compare/v2.111.2...v2.111.3) (2026-01-06)
6
10
 
7
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.111.3",
3
+ "version": "2.111.5",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -433,10 +433,10 @@ async function trackPractice(contentId, secondsPlayed, prevSession, details = {}
433
433
  }
434
434
 
435
435
  async function trackProgress(contentId, collection, currentSeconds, mediaLengthSeconds) {
436
- const progress = Math.min(
436
+ const progress = Math.max(1, Math.min(
437
437
  99,
438
438
  Math.round(((currentSeconds ?? 0) / Math.max(1, mediaLengthSeconds)) * 100)
439
- )
439
+ ))
440
440
  return saveContentProgress(contentId, collection, progress, currentSeconds)
441
441
  }
442
442
 
@@ -5,4 +5,10 @@ export default abstract class BaseSessionProvider extends BaseContextProvider {
5
5
  getSessionId(): string | null {
6
6
  return null
7
7
  }
8
+ toJSON() {
9
+ return {
10
+ 'session.id': this.getSessionId() || undefined,
11
+ 'session.client': this.getClientId(),
12
+ }
13
+ }
8
14
  }
@@ -124,7 +124,7 @@ export default class SyncManager {
124
124
 
125
125
  // can fail synchronously immediately (e.g., schema/migration validation errors)
126
126
  // or asynchronously (e.g., indexedDB errors synchronously OR asynchronously (!))
127
- const database = this.telemetry.trace({ name: 'db:init' }, this.initDatabase)
127
+ const database = this.telemetry.trace({ name: 'db:init', attributes: { ...this.context.session.toJSON() } }, this.initDatabase)
128
128
 
129
129
  Object.entries(this.storeConfigsRegistry).forEach(([table, storeConfig]) => {
130
130
  this.storesRegistry[table] = this.createStore(storeConfig, database)
@@ -77,56 +77,56 @@ export default class SyncRepository<TModel extends BaseModel> {
77
77
 
78
78
  protected async insertOne(builder: (record: TModel) => void) {
79
79
  return this.store.telemetry.trace(
80
- { name: `insertOne:${this.store.model.table}`, op: 'insert' },
80
+ { name: `insertOne:${this.store.model.table}`, op: 'insert', attributes: { ...this.context.session.toJSON() } },
81
81
  (span) => this._respondToWrite(() => this.store.insertOne(builder, span), span)
82
82
  )
83
83
  }
84
84
 
85
85
  protected async updateOneId(id: RecordId, builder: (record: TModel) => void) {
86
86
  return this.store.telemetry.trace(
87
- { name: `updateOne:${this.store.model.table}`, op: 'update' },
87
+ { name: `updateOne:${this.store.model.table}`, op: 'update', attributes: { ...this.context.session.toJSON() } },
88
88
  (span) => this._respondToWrite(() => this.store.updateOneId(id, builder, span), span)
89
89
  )
90
90
  }
91
91
 
92
92
  protected async upsertOne(id: RecordId, builder: (record: TModel) => void, { skipPush = false } = {}) {
93
93
  return this.store.telemetry.trace(
94
- { name: `upsertOne:${this.store.model.table}`, op: 'upsert' },
94
+ { name: `upsertOne:${this.store.model.table}`, op: 'upsert', attributes: { ...this.context.session.toJSON() } },
95
95
  (span) => this._respondToWrite(() => this.store.upsertOne(id, builder, span, {skipPush}), span)
96
96
  )
97
97
  }
98
98
 
99
99
  protected async upsertOneTentative(id: RecordId, builder: (record: TModel) => void) {
100
100
  return this.store.telemetry.trace(
101
- { name: `upsertOneTentative:${this.store.model.table}`, op: 'upsert' },
101
+ { name: `upsertOneTentative:${this.store.model.table}`, op: 'upsert', attributes: { ...this.context.session.toJSON() } },
102
102
  (span) => this._respondToWrite(() => this.store.upsertOneTentative(id, builder, span), span)
103
103
  )
104
104
  }
105
105
 
106
106
  protected async upsertSome(builders: Record<RecordId, (record: TModel) => void>, { skipPush = false } = {}) {
107
107
  return this.store.telemetry.trace(
108
- { name: `upsertSome:${this.store.model.table}`, op: 'upsert' },
108
+ { name: `upsertSome:${this.store.model.table}`, op: 'upsert', attributes: { ...this.context.session.toJSON() } },
109
109
  (span) => this._respondToWrite(() => this.store.upsertSome(builders, span, {skipPush}), span)
110
110
  )
111
111
  }
112
112
 
113
113
  protected async upsertSomeTentative(builders: Record<RecordId, (record: TModel) => void>, { skipPush = false } = {}) {
114
114
  return this.store.telemetry.trace(
115
- { name: `upsertSomeTentative:${this.store.model.table}`, op: 'upsert' },
115
+ { name: `upsertSomeTentative:${this.store.model.table}`, op: 'upsert', attributes: { ...this.context.session.toJSON() } },
116
116
  (span) => this._respondToWrite(() => this.store.upsertSomeTentative(builders, span, {skipPush}), span)
117
117
  )
118
118
  }
119
119
 
120
120
  protected async deleteOne(id: RecordId, { skipPush = false } = {}) {
121
121
  return this.store.telemetry.trace(
122
- { name: `delete:${this.store.model.table}`, op: 'delete' },
122
+ { name: `delete:${this.store.model.table}`, op: 'delete', attributes: { ...this.context.session.toJSON() } },
123
123
  (span) => this._respondToWriteIds(() => this.store.deleteOne(id, span, {skipPush}), span)
124
124
  )
125
125
  }
126
126
 
127
127
  protected async deleteSome(ids: RecordId[]) {
128
128
  return this.store.telemetry.trace(
129
- { name: `deleteSome:${this.store.model.table}`, op: 'delete' },
129
+ { name: `deleteSome:${this.store.model.table}`, op: 'delete', attributes: { ...this.context.session.toJSON() } },
130
130
  (span) => this._respondToWriteIds(() => this.store.deleteSome(ids, span), span)
131
131
  )
132
132
  }
@@ -43,7 +43,12 @@ export default class SyncRetry {
43
43
 
44
44
  attempt++
45
45
 
46
- const spanOptions = { ...spanOpts, name: `${spanOpts.name}:attempt:${attempt}/${this.MAX_ATTEMPTS}`, op: `${spanOpts.op}:attempt` }
46
+ const spanOptions = {
47
+ ...spanOpts,
48
+ name: `${spanOpts.name}:attempt:${attempt}/${this.MAX_ATTEMPTS}`,
49
+ op: `${spanOpts.op}:attempt`,
50
+ attributes: { ...spanOpts.attributes, attempt, ...this.context.session.toJSON() }
51
+ }
47
52
  const result = await this.telemetry.trace(spanOptions, span => {
48
53
  if (!this.context.connectivity.getValue()) {
49
54
  this.telemetry.debug('[Retry] No connectivity - skipping')
@@ -100,7 +100,7 @@ export default class SyncStore<TModel extends BaseModel = BaseModel> {
100
100
  async requestSync(reason: string) {
101
101
  inBoundary(ctx => {
102
102
  this.telemetry.trace(
103
- { name: `sync:${this.model.table}`, op: 'sync', attributes: ctx },
103
+ { name: `sync:${this.model.table}`, op: 'sync', attributes: { ...ctx, ...this.context.session.toJSON() } },
104
104
  async span => {
105
105
  let pushError: any = null
106
106
 
@@ -125,7 +125,7 @@ export default class SyncStore<TModel extends BaseModel = BaseModel> {
125
125
  async requestPush(reason: string) {
126
126
  inBoundary(ctx => {
127
127
  this.telemetry.trace(
128
- { name: `sync:${this.model.table}`, op: 'push', attributes: ctx },
128
+ { name: `sync:${this.model.table}`, op: 'push', attributes: { ...ctx, ...this.context.session.toJSON() } },
129
129
  async span => {
130
130
  await this.pushUnsyncedWithRetry(span)
131
131
  }
@@ -509,7 +509,7 @@ export default class SyncStore<TModel extends BaseModel = BaseModel> {
509
509
  {
510
510
  name: `pull:${this.model.table}:run`,
511
511
  op: 'pull:run',
512
- attributes: { table: this.model.table },
512
+ attributes: { table: this.model.table, ...this.context.session.toJSON() },
513
513
  parentSpan: span,
514
514
  },
515
515
  async (pullSpan) => {
@@ -519,7 +519,7 @@ export default class SyncStore<TModel extends BaseModel = BaseModel> {
519
519
  {
520
520
  name: `pull:${this.model.table}:run:fetch`,
521
521
  op: 'pull:run:fetch',
522
- attributes: { lastFetchToken: lastFetchToken ?? undefined },
522
+ attributes: { lastFetchToken: lastFetchToken ?? undefined, ...this.context.session.toJSON() },
523
523
  parentSpan: pullSpan,
524
524
  },
525
525
  () => this.puller(this.context.session, lastFetchToken, this.runScope.signal)
@@ -542,7 +542,7 @@ export default class SyncStore<TModel extends BaseModel = BaseModel> {
542
542
  {
543
543
  name: `push:${this.model.table}`,
544
544
  op: 'push:run',
545
- attributes: { table: this.model.table },
545
+ attributes: { table: this.model.table, ...this.context.session.toJSON() },
546
546
  parentSpan,
547
547
  },
548
548
  async (pushSpan) => {
@@ -552,6 +552,7 @@ export default class SyncStore<TModel extends BaseModel = BaseModel> {
552
552
  {
553
553
  name: `push:${this.model.table}:run:fetch`,
554
554
  op: 'push:run:fetch',
555
+ attributes: { ...this.context.session.toJSON() },
555
556
  parentSpan: pushSpan,
556
557
  },
557
558
  () => this.pusher(this.context.session, payload, this.runScope.signal)
@@ -706,7 +707,7 @@ export default class SyncStore<TModel extends BaseModel = BaseModel> {
706
707
  work: (writer: WriterInterface) => Promise<T>
707
708
  ): Promise<T> {
708
709
  return this.telemetry.trace(
709
- { name: `write:${this.model.table}`, op: 'write', parentSpan },
710
+ { name: `write:${this.model.table}`, op: 'write', parentSpan, attributes: { ...this.context.session.toJSON() } },
710
711
  (writeSpan) => {
711
712
  return this.db.write(writer =>
712
713
  this.telemetry.trace(
@@ -714,6 +715,7 @@ export default class SyncStore<TModel extends BaseModel = BaseModel> {
714
715
  name: `write:generate:${this.model.table}`,
715
716
  op: 'write:generate',
716
717
  parentSpan: writeSpan,
718
+ attributes: { ...this.context.session.toJSON() },
717
719
  },
718
720
  () => work(writer)
719
721
  )
@@ -36,9 +36,7 @@ export const syncSentryBeforeSendTransaction: ReturnsUndefined<NonNullable<Sentr
36
36
  return undefined
37
37
  }
38
38
 
39
- // sentry doesn't bother to expose your chosen environment in tracesSampler
40
- // so we have to make consumers pass in our greedy option
41
- export const createSyncSentryTracesSampler = () => {
39
+ export const createSyncSentryTracesSampler = (sampleRate = 0.1) => {
42
40
  const sampler: ReturnsUndefined<NonNullable<SentryBrowserOptions['tracesSampler']>> = (context) => {
43
41
  if (!context.name.startsWith(SYNC_TELEMETRY_TRACE_PREFIX)) {
44
42
  return undefined
@@ -51,7 +49,7 @@ export const createSyncSentryTracesSampler = () => {
51
49
  }
52
50
 
53
51
  if (attributes?.userId) {
54
- return userBucketedSampler(attributes.userId as string | number, 0.1)
52
+ return userBucketedSampler(attributes.userId as string | number, sampleRate)
55
53
  }
56
54
 
57
55
  return undefined
@@ -1,9 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(rg:*)",
5
- "Bash(npm run lint:*)"
6
- ],
7
- "deny": []
8
- }
9
- }