musora-content-services 2.138.4 → 2.139.1

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,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.139.1](https://github.com/railroadmedia/musora-content-services/compare/v2.139.0...v2.139.1) (2026-03-04)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **BEH-1530:** cache 204 daily session response ([#852](https://github.com/railroadmedia/musora-content-services/issues/852)) ([9ce8013](https://github.com/railroadmedia/musora-content-services/commit/9ce80135799733e428bc7f7588615bbf80222b4f))
11
+
12
+ ## [2.139.0](https://github.com/railroadmedia/musora-content-services/compare/v2.138.4...v2.139.0) (2026-03-04)
13
+
14
+
15
+ ### Features
16
+
17
+ * adds SQLITE_FULL error simulation ([#854](https://github.com/railroadmedia/musora-content-services/issues/854)) ([fc1cf71](https://github.com/railroadmedia/musora-content-services/commit/fc1cf7185ff6a07b08f77eeee0fa5ec2f2fb4197))
18
+
5
19
  ### [2.138.4](https://github.com/railroadmedia/musora-content-services/compare/v2.138.3...v2.138.4) (2026-03-04)
6
20
 
7
21
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.138.4",
3
+ "version": "2.139.1",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -102,9 +102,9 @@ export async function updateDailySession(
102
102
  keepFirstLearningPath: keepFirstLearningPath,
103
103
  }
104
104
  try {
105
- const response = (await POST(url, body)) as DailySessionResponse
105
+ const response = (await POST(url, body)) as DailySessionResponse|''
106
106
 
107
- if (response) { // refresh cached value
107
+ if (response || response === '') { // refresh cached value
108
108
  const urlGet: string = `${LEARNING_PATHS_PATH}/daily-session/get?brand=${brand}&userDate=${encodeURIComponent(dateWithTimezone)}`
109
109
  dataPromiseGET(urlGet, true).then(() => {
110
110
  dailySessionPromise = null
@@ -112,7 +112,7 @@ export async function updateDailySession(
112
112
 
113
113
  }
114
114
 
115
- return response
115
+ return (response !== '' ? response : null)
116
116
  } catch (error: any) {
117
117
  return null
118
118
  }
@@ -497,23 +497,23 @@ interface completeLearningPathIntroVideo {
497
497
  * @returns {Promise<Object|null>} response.intro_video_response - The intro video completion response or null if already completed.
498
498
  * @returns {Promise<void>} response.learning_path_reset_response - The reset learning path response.
499
499
  * @returns {Promise<Object[]>} response.lesson_import_response - The responses for completing each content_id within the learning path.
500
+ * @returns {Promise<Object|null>} response.update_dailies_response - The updated daily session if it was changed.
500
501
  */
501
502
  export async function completeLearningPathIntroVideo(
502
503
  introVideoId: number,
503
504
  learningPathId: number,
504
505
  lessonsToImport: number[] | null,
505
- brand: string | null
506
+ brand: string
506
507
  ) {
507
508
  let response = {} as completeLearningPathIntroVideo
508
-
509
509
  const collection: CollectionObject = { id: learningPathId, type: COLLECTION_TYPE.LEARNING_PATH }
510
510
 
511
511
  if (!lessonsToImport) {
512
512
  response.learning_path_reset_response = await resetIfPossible(learningPathId, collection)
513
513
  } else {
514
514
  response.lesson_import_response = await contentStatusCompletedMany(lessonsToImport, collection)
515
- const activePath = await getActivePath(brand)
516
515
 
516
+ const activePath = await getActivePath(brand)
517
517
  if (activePath.active_learning_path_id === learningPathId) {
518
518
  response.update_dailies_response = await updateDailySession(brand, new Date(), true)
519
519
  }
@@ -2,8 +2,8 @@ import schema from '../schema'
2
2
  import type { SyncUserScope } from '../index'
3
3
  import { SyncError } from '../errors'
4
4
 
5
- import type SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite'
6
- import type LokiJSAdapter from '@nozbe/watermelondb/adapters/lokijs'
5
+ import type { default as SQLiteAdapter, SQLiteExtensions } from './sqlite'
6
+ import type LokiJSAdapter from './lokijs'
7
7
 
8
8
  export type DatabaseAdapter = SQLiteAdapter | LokiJSAdapter
9
9
 
@@ -13,8 +13,9 @@ type LokiJSAdapterOptions = ConstructorParameters<typeof LokiJSAdapter>[0]
13
13
  type DatabaseAdapterOptions = SQLiteAdapterOptions & LokiJSAdapterOptions
14
14
 
15
15
  export default function syncAdapterFactory<T extends DatabaseAdapter>(
16
- AdapterClass: new (options: DatabaseAdapterOptions) => T,
17
- opts: Omit<DatabaseAdapterOptions, 'schema' | 'migrations'>
16
+ AdapterClass: new (options: DatabaseAdapterOptions, extensions?: SQLiteExtensions) => T,
17
+ opts: Omit<DatabaseAdapterOptions, 'schema' | 'migrations'>,
18
+ extensions?: SQLiteExtensions
18
19
  ): (userScope: SyncUserScope) => T {
19
20
  // IMPORTANT: we rely on namespaced databases to prevent data clobbering
20
21
  // when localStorage.userId somehow changes outside of an explicit, app-managed logout
@@ -31,11 +32,13 @@ export default function syncAdapterFactory<T extends DatabaseAdapter>(
31
32
  throw new SyncError('User ID is required to construct database adapter')
32
33
  }
33
34
 
34
- return new AdapterClass({
35
+ const adapter = new AdapterClass({
35
36
  ...opts,
36
37
  dbName: `musora:sync:${userScope.initialId}`,
37
38
  schema,
38
39
  migrations: undefined
39
- })
40
+ }, extensions)
41
+
42
+ return adapter
40
43
  }
41
44
  }
@@ -1 +1,51 @@
1
- export { default } from '@nozbe/watermelondb/adapters/sqlite'
1
+ import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite'
2
+
3
+ export default class SQLiteErrorAwareAdapter extends SQLiteAdapter {
4
+ private extensions: SQLiteExtensions;
5
+
6
+ constructor(options: any, extensions: SQLiteExtensions = {}) {
7
+ super(options);
8
+ this.extensions = extensions;
9
+ }
10
+
11
+ batch(operations: any[], callback: any) {
12
+ const onFullError = this.extensions.onFullError;
13
+ super.batch(operations, (result: any) => {
14
+ if (result.error && isSQLiteFullError(result.error)) {
15
+ onFullError?.(result.error);
16
+ }
17
+ callback(result);
18
+ });
19
+ }
20
+ }
21
+
22
+ export class FullFailingSQLiteAdapter extends SQLiteErrorAwareAdapter {
23
+ constructor(options: any, extensions?: { onFullError?: (error: Error) => void }) {
24
+ super(options, extensions);
25
+
26
+ // Wrap the dispatcher to intercept batch calls and simulate errors at dispatcher level
27
+ const originalDispatcher = (this as any)._dispatcher;
28
+ const originalCall = originalDispatcher.call.bind(originalDispatcher);
29
+
30
+ originalDispatcher.call = (methodName: string, args: any[], callback: any) => {
31
+ if (methodName === 'batch') {
32
+ // Simulate dispatcher-level error
33
+ setTimeout(() => {
34
+ callback({ error: new Error('sqlite error 13 (database or disk is full)') });
35
+ }, 0);
36
+ return;
37
+ }
38
+ // For all other methods, use original dispatcher
39
+ return originalCall(methodName, args, callback);
40
+ };
41
+ }
42
+ }
43
+
44
+ function isSQLiteFullError(error: Error) {
45
+ const message = error.message.toLowerCase();
46
+ return message.includes('sqlite error 13') || message.includes('database or disk is full');
47
+ }
48
+
49
+ export type SQLiteExtensions = {
50
+ onFullError?: (err: Error) => void
51
+ }