musora-content-services 2.125.0 → 2.126.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 (207) hide show
  1. package/.coderabbit.yaml +0 -0
  2. package/.editorconfig +0 -0
  3. package/.github/pull_request_template.md +0 -0
  4. package/.github/workflows/conventional-commits.yaml +0 -0
  5. package/.github/workflows/docs.js.yml +0 -0
  6. package/.github/workflows/node.js.yml +0 -0
  7. package/.prettierignore +0 -0
  8. package/.prettierrc +0 -0
  9. package/CHANGELOG.md +8 -0
  10. package/README.md +0 -0
  11. package/babel.config.cjs +0 -0
  12. package/jest.config.js +0 -0
  13. package/jsdoc.json +0 -0
  14. package/package.json +1 -1
  15. package/src/constants/award-assets.js +0 -0
  16. package/src/constants/membership-permissions.ts +0 -0
  17. package/src/filterBuilder.js +0 -0
  18. package/src/infrastructure/http/HttpClient.ts +0 -0
  19. package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
  20. package/src/infrastructure/http/index.ts +0 -0
  21. package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
  22. package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
  23. package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
  24. package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
  25. package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
  26. package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
  27. package/src/lib/ads/monoid.ts +0 -0
  28. package/src/lib/ads/semigroup.ts +0 -0
  29. package/src/lib/brands.ts +0 -0
  30. package/src/lib/lastUpdated.js +0 -0
  31. package/src/lib/sanity/filter.ts +0 -0
  32. package/src/lib/sanity/query.ts +0 -0
  33. package/src/services/api/types.js +0 -0
  34. package/src/services/api/types.ts +0 -0
  35. package/src/services/awards/award-callbacks.js +0 -0
  36. package/src/services/awards/internal/.indexignore +0 -0
  37. package/src/services/awards/internal/award-definitions.js +0 -0
  38. package/src/services/awards/internal/award-events.js +0 -0
  39. package/src/services/awards/internal/award-manager.js +0 -0
  40. package/src/services/awards/internal/certificate-builder.js +0 -0
  41. package/src/services/awards/internal/completion-data-generator.js +0 -0
  42. package/src/services/awards/internal/content-progress-observer.js +0 -0
  43. package/src/services/awards/internal/image-utils.js +0 -0
  44. package/src/services/awards/internal/message-generator.js +0 -0
  45. package/src/services/awards/internal/types.js +0 -0
  46. package/src/services/awards/types.d.ts +0 -0
  47. package/src/services/awards/types.js +0 -0
  48. package/src/services/config.js +0 -0
  49. package/src/services/content/artist.ts +0 -0
  50. package/src/services/content/content.ts +0 -0
  51. package/src/services/content/genre.ts +0 -0
  52. package/src/services/content/instructor.ts +0 -0
  53. package/src/services/content-org/content-org.js +0 -0
  54. package/src/services/content-org/guided-courses.ts +0 -0
  55. package/src/services/content-org/playlists-types.js +0 -0
  56. package/src/services/content-org/playlists.js +0 -0
  57. package/src/services/contentAggregator.js +0 -0
  58. package/src/services/contentLikes.js +0 -0
  59. package/src/services/dataContext.js +0 -0
  60. package/src/services/dateUtils.js +0 -0
  61. package/src/services/eventsAPI.js +0 -0
  62. package/src/services/forums/categories.ts +0 -0
  63. package/src/services/forums/forums.ts +0 -0
  64. package/src/services/forums/threads.ts +0 -0
  65. package/src/services/forums/types.ts +0 -0
  66. package/src/services/gamification/awards.ts +0 -0
  67. package/src/services/gamification/gamification.js +0 -0
  68. package/src/services/imageSRCBuilder.js +0 -0
  69. package/src/services/imageSRCVerify.js +0 -0
  70. package/src/services/liveTesting.ts +0 -0
  71. package/src/services/permissions/PermissionsAdapterFactory.ts +0 -0
  72. package/src/services/permissions/PermissionsV1Adapter.ts +0 -0
  73. package/src/services/permissions/PermissionsV2Adapter.ts +0 -0
  74. package/src/services/permissions/README.md +0 -0
  75. package/src/services/permissions/index.ts +0 -0
  76. package/src/services/progress-events.js +0 -0
  77. package/src/services/progress-row/rows/.indexignore +0 -0
  78. package/src/services/progress-row/rows/method-card.js +0 -0
  79. package/src/services/railcontent.js +0 -0
  80. package/src/services/reporting/README.md +0 -0
  81. package/src/services/reporting/types.ts +0 -0
  82. package/src/services/sentry/.indexignore +0 -0
  83. package/src/services/sentry/index.ts +0 -0
  84. package/src/services/sync/.indexignore +0 -0
  85. package/src/services/sync/adapters/factory.ts +0 -0
  86. package/src/services/sync/adapters/lokijs.ts +0 -0
  87. package/src/services/sync/adapters/sqlite.ts +0 -0
  88. package/src/services/sync/context/index.ts +0 -0
  89. package/src/services/sync/context/providers/base.ts +0 -0
  90. package/src/services/sync/context/providers/connectivity.ts +0 -0
  91. package/src/services/sync/context/providers/durability.ts +0 -0
  92. package/src/services/sync/context/providers/index.ts +0 -0
  93. package/src/services/sync/context/providers/session.ts +0 -0
  94. package/src/services/sync/context/providers/tabs.ts +0 -0
  95. package/src/services/sync/context/providers/visibility.ts +0 -0
  96. package/src/services/sync/database/factory.ts +0 -0
  97. package/src/services/sync/effects/index.ts +1 -0
  98. package/src/services/sync/effects/logout-warning.ts +0 -0
  99. package/src/services/sync/effects/push-failure-notification.ts +34 -0
  100. package/src/services/sync/errors/boundary.ts +0 -0
  101. package/src/services/sync/errors/index.ts +0 -0
  102. package/src/services/sync/errors/validators.ts +0 -0
  103. package/src/services/sync/fetch.ts +83 -14
  104. package/src/services/sync/index.ts +0 -0
  105. package/src/services/sync/manager.ts +4 -0
  106. package/src/services/sync/models/Base.ts +0 -0
  107. package/src/services/sync/models/ContentLike.ts +0 -0
  108. package/src/services/sync/models/ContentProgress.ts +0 -0
  109. package/src/services/sync/models/Practice.ts +0 -0
  110. package/src/services/sync/models/PracticeDayNote.ts +0 -0
  111. package/src/services/sync/models/UserAwardProgress.ts +0 -0
  112. package/src/services/sync/models/index.ts +0 -0
  113. package/src/services/sync/repositories/base.ts +0 -0
  114. package/src/services/sync/repositories/content-likes.ts +0 -0
  115. package/src/services/sync/repositories/index.ts +0 -0
  116. package/src/services/sync/repositories/practice-day-notes.ts +0 -0
  117. package/src/services/sync/repositories/practices.ts +0 -0
  118. package/src/services/sync/repositories/user-award-progress.ts +0 -0
  119. package/src/services/sync/repository-proxy.ts +0 -0
  120. package/src/services/sync/retry.ts +12 -4
  121. package/src/services/sync/run-scope.ts +0 -0
  122. package/src/services/sync/schema/index.ts +0 -0
  123. package/src/services/sync/serializers/index.ts +0 -0
  124. package/src/services/sync/serializers/model.ts +0 -0
  125. package/src/services/sync/serializers/raw.ts +0 -0
  126. package/src/services/sync/store/index.ts +26 -17
  127. package/src/services/sync/store/push-coalescer.ts +0 -0
  128. package/src/services/sync/store-configs.ts +0 -0
  129. package/src/services/sync/strategies/base.ts +0 -0
  130. package/src/services/sync/strategies/index.ts +0 -0
  131. package/src/services/sync/strategies/initial.ts +0 -0
  132. package/src/services/sync/strategies/polling.ts +0 -0
  133. package/src/services/sync/telemetry/flood-prevention.ts +0 -0
  134. package/src/services/sync/telemetry/index.ts +5 -3
  135. package/src/services/sync/telemetry/sampling.ts +0 -0
  136. package/src/services/sync/utils/event-emitter.ts +0 -0
  137. package/src/services/sync/utils/index.ts +0 -0
  138. package/src/services/sync/utils/throttle.ts +0 -0
  139. package/src/services/sync/utils/timers.ts +0 -0
  140. package/src/services/types.js +0 -0
  141. package/src/services/user/account.ts +0 -0
  142. package/src/services/user/chat.js +0 -0
  143. package/src/services/user/interests.js +0 -0
  144. package/src/services/user/management.js +0 -0
  145. package/src/services/user/memberships.ts +0 -0
  146. package/src/services/user/notifications.js +0 -0
  147. package/src/services/user/onboarding.ts +0 -0
  148. package/src/services/user/payments.ts +0 -0
  149. package/src/services/user/permissions.js +0 -0
  150. package/src/services/user/profile.js +0 -0
  151. package/src/services/user/streakCalculator.ts +0 -0
  152. package/src/services/user/types.d.ts +0 -0
  153. package/src/services/user/types.js +0 -0
  154. package/src/services/user/user-management-system.js +0 -0
  155. package/test/HttpClient.test.js +0 -0
  156. package/test/awards/award-alacarte-observer.test.js +0 -0
  157. package/test/awards/award-auto-refresh.test.js +0 -0
  158. package/test/awards/award-calculations.test.js +0 -0
  159. package/test/awards/award-certificate-display.test.js +0 -0
  160. package/test/awards/award-collection-edge-cases.test.js +0 -0
  161. package/test/awards/award-collection-filtering.test.js +0 -0
  162. package/test/awards/award-completion-flow.test.js +0 -0
  163. package/test/awards/award-exclusion-handling.test.js +0 -0
  164. package/test/awards/award-multi-lesson.test.js +0 -0
  165. package/test/awards/award-observer-integration.test.js +0 -0
  166. package/test/awards/award-query-messages.test.js +0 -0
  167. package/test/awards/award-user-collection.test.js +0 -0
  168. package/test/awards/duplicate-prevention.test.js +0 -0
  169. package/test/awards/helpers/completion-mock.js +0 -0
  170. package/test/awards/helpers/index.js +0 -0
  171. package/test/awards/helpers/mock-setup.js +0 -0
  172. package/test/awards/helpers/progress-emitter.js +0 -0
  173. package/test/awards/message-generator.test.js +0 -0
  174. package/test/content.test.js +0 -0
  175. package/test/contentLikes.test.js +0 -0
  176. package/test/contentProgress.test.js +0 -0
  177. package/test/dataContext.test.js +0 -0
  178. package/test/forum.test.js +0 -0
  179. package/test/imageSRCBuilder.test.js +0 -0
  180. package/test/imageSRCVerify.test.js +0 -0
  181. package/test/initializeTests.js +0 -0
  182. package/test/learningPaths.test.js +0 -0
  183. package/test/lib/__snapshots__/filter.test.ts.snap +0 -0
  184. package/test/lib/filter.test.ts +0 -0
  185. package/test/lib/lastUpdated.test.js +0 -0
  186. package/test/lib/query.test.ts +0 -0
  187. package/test/live/contentProgressLive.test.js +0 -0
  188. package/test/live/railcontentLive.test.js +0 -0
  189. package/test/localStorageMock.js +0 -0
  190. package/test/log.js +0 -0
  191. package/test/mockData/award-definitions.js +0 -0
  192. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  193. package/test/mockData/mockData_progress_content.json +0 -0
  194. package/test/mockData/mockData_sanity_progress_content.json +0 -0
  195. package/test/mockData/mockData_user_practices.json +0 -0
  196. package/test/notifications.test.js +0 -0
  197. package/test/progressRows.test.js +0 -0
  198. package/test/sanityQueryService.test.js +0 -0
  199. package/test/streakMessage.test.js +0 -0
  200. package/test/sync/adapter.ts +0 -0
  201. package/test/sync/models/award-database-integration.test.js +0 -0
  202. package/test/user/permissions.test.js +0 -0
  203. package/test/userActivity.test.js +0 -0
  204. package/tools/generate-index.cjs +0 -0
  205. package/.claude/settings.local.json +0 -8
  206. package/TECHNICAL_DEBT.md +0 -482
  207. package/url, +0 -0
package/.coderabbit.yaml CHANGED
File without changes
package/.editorconfig CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
package/.prettierignore CHANGED
File without changes
package/.prettierrc CHANGED
File without changes
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
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.126.0](https://github.com/railroadmedia/musora-content-services/compare/v2.125.0...v2.126.0) (2026-01-29)
6
+
7
+
8
+ ### Features
9
+
10
+ * adds sync store push blocking ([#754](https://github.com/railroadmedia/musora-content-services/issues/754)) ([e9a7c23](https://github.com/railroadmedia/musora-content-services/commit/e9a7c23c91f5e707c08c4e0765af52213ba6263b))
11
+ * melon push failure notifs ([#753](https://github.com/railroadmedia/musora-content-services/issues/753)) ([cce60ad](https://github.com/railroadmedia/musora-content-services/commit/cce60ad3e618106473fd0c7da141aebaced835d5))
12
+
5
13
  ## [2.125.0](https://github.com/railroadmedia/musora-content-services/compare/v2.122.7...v2.125.0) (2026-01-29)
6
14
 
7
15
 
package/README.md CHANGED
File without changes
package/babel.config.cjs CHANGED
File without changes
package/jest.config.js CHANGED
File without changes
package/jsdoc.json CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.125.0",
3
+ "version": "2.126.0",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/src/lib/brands.ts CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -4,3 +4,4 @@ import type SyncStore from "../store"
4
4
  export type SyncEffect = (context: SyncContext, stores: SyncStore[]) => () => void
5
5
 
6
6
  export { default as createLogoutWarningEffect } from './logout-warning'
7
+ export { default as createPushFailureNotificationEffect } from './push-failure-notification'
File without changes
@@ -0,0 +1,34 @@
1
+ import { type SyncEffect } from '.'
2
+
3
+ const NOTIFICATION_COOLDOWN = 60_000 * 10 // 10 mins
4
+ const MUTE_PERIOD = 60_000 * 60 * 3 // 3 hours
5
+
6
+ const createPushFailureNotificationEffect = (notifyCallback: (opts: { mute: () => void }) => void) => {
7
+ let lastNotifiedAt = 0
8
+ let mutedUntil = 0
9
+
10
+ const mute = () => {
11
+ mutedUntil = Date.now() + MUTE_PERIOD
12
+ }
13
+
14
+ const pushFailureToast: SyncEffect = function (_context, stores) {
15
+ const maybeNotify = () => {
16
+ const now = Date.now()
17
+ if (now - lastNotifiedAt < NOTIFICATION_COOLDOWN) return
18
+ if (mutedUntil && now < mutedUntil) return
19
+
20
+ lastNotifiedAt = now
21
+ notifyCallback({ mute })
22
+ }
23
+
24
+ const teardowns = stores.map((store) => store.on('failedPush', maybeNotify))
25
+
26
+ return () => {
27
+ teardowns.forEach((teardown) => teardown())
28
+ }
29
+ }
30
+
31
+ return pushFailureToast
32
+ }
33
+
34
+ export default createPushFailureNotificationEffect
File without changes
File without changes
File without changes
@@ -4,7 +4,27 @@ import { EpochMs } from "."
4
4
  import { globalConfig } from '../config.js'
5
5
  import { RecordId } from "@nozbe/watermelondb"
6
6
  import BaseModel from "./models/Base"
7
- import { BaseSessionProvider } from "./context/providers"
7
+ import SyncContext from "./context"
8
+ import { SyncTelemetry } from "./telemetry"
9
+
10
+ export type BlockingState = {
11
+ enabled: boolean
12
+ }
13
+ export type SyncPull = (
14
+ tableName: string,
15
+ intendedUserId: number,
16
+ context: SyncContext,
17
+ signal: AbortSignal,
18
+ previousFetchToken: SyncToken | null
19
+ ) => Promise<SyncPullResponse>
20
+ export type SyncPush = (
21
+ tableName: string,
22
+ intendedUserId: number,
23
+ context: SyncContext,
24
+ payload: PushPayload,
25
+ signal: AbortSignal,
26
+ blockingState: BlockingState
27
+ ) => Promise<SyncPushResponse>
8
28
 
9
29
  interface RawPullResponse {
10
30
  meta: {
@@ -23,7 +43,7 @@ interface RawPushResponse {
23
43
  }
24
44
 
25
45
  export type SyncResponse = SyncPushResponse | SyncPullResponse
26
- export type SyncPushResponse = SyncPushSuccessResponse | SyncPushFetchFailureResponse | SyncPushFailureResponse
46
+ export type SyncPushResponse = SyncPushSuccessResponse | SyncPushFetchFailureResponse | SyncPushAbortResponse | SyncPushBlockedResponse | SyncPushFailureResponse
27
47
 
28
48
  type SyncPushSuccessResponse = SyncResponseBase & {
29
49
  ok: true
@@ -34,6 +54,15 @@ type SyncPushFetchFailureResponse = SyncResponseBase & {
34
54
  failureType: 'fetch'
35
55
  isRetryable: boolean
36
56
  }
57
+ type SyncPushAbortResponse = SyncResponseBase & {
58
+ ok: false,
59
+ failureType: 'abort'
60
+ }
61
+ type SyncPushBlockedResponse = SyncResponseBase & {
62
+ ok: false,
63
+ failureType: 'blocked'
64
+ isRetryable: boolean
65
+ }
37
66
  type SyncPushFailureResponse = SyncResponseBase & {
38
67
  ok: false,
39
68
  failureType: 'error'
@@ -63,7 +92,7 @@ interface SyncStorePushResultBase {
63
92
  type: 'success' | 'failure'
64
93
  }
65
94
 
66
- export type SyncPullResponse = SyncPullSuccessResponse | SyncPullFailureResponse | SyncPullFetchFailureResponse
95
+ export type SyncPullResponse = SyncPullSuccessResponse | SyncPullFailureResponse | SyncPullAbortResponse | SyncPullFetchFailureResponse
67
96
 
68
97
  type SyncPullSuccessResponse = SyncResponseBase & {
69
98
  ok: true
@@ -82,6 +111,10 @@ type SyncPullFailureResponse = SyncResponseBase & {
82
111
  failureType: 'error'
83
112
  originalError: Error
84
113
  }
114
+ type SyncPullAbortResponse = SyncResponseBase & {
115
+ ok: false,
116
+ failureType: 'abort'
117
+ }
85
118
  export interface SyncResponseBase {
86
119
  ok: boolean
87
120
  }
@@ -118,8 +151,8 @@ interface ServerPushPayload {
118
151
  }[]
119
152
  }
120
153
 
121
- export function makeFetchRequest(input: RequestInfo, init?: RequestInit): (userId: number, session: BaseSessionProvider) => Request {
122
- return (userId, session) => new Request(globalConfig.baseUrl + input, {
154
+ export function makeFetchRequest(input: RequestInfo, init?: RequestInit): (userId: number, context: SyncContext) => Request {
155
+ return (userId, context) => new Request(globalConfig.baseUrl + input, {
123
156
  ...init,
124
157
  headers: {
125
158
  ...init?.headers,
@@ -127,18 +160,18 @@ export function makeFetchRequest(input: RequestInfo, init?: RequestInit): (userI
127
160
  'Authorization': `Bearer ${globalConfig.sessionConfig.token}`
128
161
  } : {},
129
162
  'Content-Type': 'application/json',
130
- 'X-Sync-Client-Id': session.getClientId(),
131
- ...(session.getSessionId() ? {
132
- 'X-Sync-Client-Session-Id': session.getSessionId()!
163
+ 'X-Sync-Client-Id': context.session.getClientId(),
164
+ ...(context.session.getSessionId() ? {
165
+ 'X-Sync-Client-Session-Id': context.session.getSessionId()!
133
166
  } : {}),
134
167
  'X-Sync-Intended-User-Id': userId.toString()
135
168
  }
136
169
  })
137
170
  }
138
171
 
139
- export function handlePull(callback: (userId: number, session: BaseSessionProvider) => Request) {
140
- return async function(userId: number, session: BaseSessionProvider, lastFetchToken: SyncToken | null, signal?: AbortSignal): Promise<SyncPullResponse> {
141
- const generatedRequest = callback(userId, session)
172
+ export function handlePull(callback: (userId: number, context: SyncContext) => Request): SyncPull {
173
+ return async function(_tableName, userId, context, signal, lastFetchToken) {
174
+ const generatedRequest = callback(userId, context)
142
175
  const url = serializePullUrlQuery(generatedRequest.url, lastFetchToken)
143
176
  const request = new Request(url, {
144
177
  credentials: 'include',
@@ -150,6 +183,19 @@ export function handlePull(callback: (userId: number, session: BaseSessionProvid
150
183
  try {
151
184
  response = await fetch(request)
152
185
  } catch (e) {
186
+ if (e.name === 'AbortError') {
187
+ return {
188
+ ok: false,
189
+ failureType: 'abort'
190
+ }
191
+ } else if (e instanceof TypeError) {
192
+ return {
193
+ ok: false,
194
+ failureType: 'fetch',
195
+ isRetryable: context.connectivity.getValue() !== false
196
+ }
197
+ }
198
+
153
199
  return {
154
200
  ok: false,
155
201
  failureType: 'error',
@@ -190,9 +236,9 @@ export function handlePull(callback: (userId: number, session: BaseSessionProvid
190
236
  }
191
237
  }
192
238
 
193
- export function handlePush(callback: (userId: number, session: BaseSessionProvider) => Request) {
194
- return async function(userId: number, session: BaseSessionProvider, payload: PushPayload, signal?: AbortSignal): Promise<SyncPushResponse> {
195
- const generatedRequest = callback(userId, session)
239
+ export function handlePush(callback: (userId: number, context: SyncContext) => Request): SyncPush {
240
+ return async function(tableName, userId, context, payload, signal, blockingState) {
241
+ const generatedRequest = callback(userId, context)
196
242
  const serverPayload = serializePushPayload(payload)
197
243
  const request = new Request(generatedRequest, {
198
244
  credentials: 'include',
@@ -200,10 +246,33 @@ export function handlePush(callback: (userId: number, session: BaseSessionProvid
200
246
  signal
201
247
  })
202
248
 
249
+ if (blockingState.enabled) {
250
+ SyncTelemetry.getInstance().debug(`[sync:${tableName}] Push blocked`)
251
+ await new Promise(resolve => setTimeout(resolve, Math.random() * 1000 + 30))
252
+ return {
253
+ ok: false,
254
+ failureType: 'blocked',
255
+ isRetryable: true
256
+ }
257
+ }
258
+
203
259
  let response: Response | null = null
204
260
  try {
205
261
  response = await fetch(request)
206
262
  } catch (e) {
263
+ if (e.name === 'AbortError') {
264
+ return {
265
+ ok: false,
266
+ failureType: 'abort'
267
+ }
268
+ } else if (e instanceof TypeError) {
269
+ return {
270
+ ok: false,
271
+ failureType: 'fetch',
272
+ isRetryable: context.connectivity.getValue() !== false
273
+ }
274
+ }
275
+
207
276
  return {
208
277
  ok: false,
209
278
  failureType: 'error',
File without changes
@@ -275,6 +275,10 @@ export default class SyncManager {
275
275
  return store as unknown as SyncStore<TModel>
276
276
  }
277
277
 
278
+ getAllStores() {
279
+ return this.storesRegistry
280
+ }
281
+
278
282
  getTelemetry() {
279
283
  return this.telemetry
280
284
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -11,7 +11,7 @@ export default class SyncRetry {
11
11
  private backoffUntil = 0
12
12
  private failureCount = 0
13
13
 
14
- private unsubscribeConnectivity: () => void
14
+ private unsubscribeConnectivity: (() => void) | null = null
15
15
 
16
16
  constructor(private readonly context: SyncContext, private readonly telemetry: SyncTelemetry) {}
17
17
 
@@ -26,13 +26,18 @@ export default class SyncRetry {
26
26
 
27
27
  stop() {
28
28
  this.unsubscribeConnectivity?.()
29
+ this.unsubscribeConnectivity = null
29
30
  }
30
31
 
31
32
  /**
32
33
  * Runs the given syncFn with automatic retries.
33
34
  * Returns the first successful result or the last failed result after retries.
34
35
  */
35
- async request<T extends SyncResponse>(spanOpts: StartSpanOptions, syncFn: (span: Span) => Promise<T>) {
36
+ async request<T extends SyncResponse>(
37
+ spanOpts: StartSpanOptions,
38
+ syncFn: (span: Span) => Promise<T>,
39
+ options: { onFail?: () => void } = {}
40
+ ) {
36
41
  let attempt = 0
37
42
 
38
43
  while (true) {
@@ -64,9 +69,12 @@ export default class SyncRetry {
64
69
  this.resetBackoff()
65
70
  return result
66
71
  } else {
67
- if (result.failureType === 'fetch' && result.isRetryable) {
72
+ if ('isRetryable' in result && result.isRetryable) {
68
73
  this.scheduleBackoff()
69
- if (attempt >= this.MAX_ATTEMPTS) return result
74
+ if (attempt >= this.MAX_ATTEMPTS) {
75
+ options.onFail?.()
76
+ return result
77
+ }
70
78
  } else {
71
79
  return result
72
80
  }
File without changes
File without changes
File without changes
File without changes
File without changes