musora-content-services 2.89.0 → 2.92.3
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 +31 -0
- package/docs/ContentOrganization.html +2 -2
- package/docs/Forums.html +2 -2
- package/docs/Gamification.html +2 -2
- package/docs/TestUser.html +2 -2
- package/docs/UserManagementSystem.html +2 -2
- package/docs/api_types.js.html +2 -2
- package/docs/config.js.html +2 -2
- package/docs/content-org_content-org.js.html +2 -2
- package/docs/content-org_guided-courses.ts.html +2 -2
- package/docs/content-org_learning-paths.ts.html +52 -40
- package/docs/content-org_playlists-types.js.html +2 -2
- package/docs/content-org_playlists.js.html +2 -2
- package/docs/content.js.html +2 -2
- package/docs/content_artist.ts.html +2 -2
- package/docs/content_genre.ts.html +2 -2
- package/docs/content_instructor.ts.html +2 -2
- package/docs/forums_categories.ts.html +2 -2
- package/docs/forums_forums.ts.html +2 -2
- package/docs/forums_posts.ts.html +2 -2
- package/docs/forums_threads.ts.html +2 -2
- package/docs/gamification_awards.ts.html +2 -2
- package/docs/gamification_gamification.js.html +2 -2
- package/docs/global.html +2 -2
- package/docs/index.html +2 -2
- package/docs/liveTesting.ts.html +2 -2
- package/docs/module-Accounts.html +2 -2
- package/docs/module-Artist.html +2 -2
- package/docs/module-Awards.html +2 -2
- package/docs/module-Config.html +2 -2
- package/docs/module-Content-Services-V2.html +2 -2
- package/docs/module-Forums.html +2 -2
- package/docs/module-Genre.html +2 -2
- package/docs/module-GuidedCourses.html +2 -2
- package/docs/module-Instructor.html +2 -2
- package/docs/module-Interests.html +2 -2
- package/docs/module-LearningPaths.html +269 -143
- package/docs/module-Onboarding.html +3 -3
- package/docs/module-Payments.html +2 -2
- package/docs/module-Permissions.html +2 -2
- package/docs/module-Playlists.html +2 -2
- package/docs/module-ProgressRow.html +2 -2
- package/docs/module-Railcontent-Services.html +34 -893
- package/docs/module-Sanity-Services.html +2 -2
- package/docs/module-Sessions.html +2 -2
- package/docs/module-UserActivity.html +70 -116
- package/docs/module-UserChat.html +2 -2
- package/docs/module-UserManagement.html +2 -2
- package/docs/module-UserMemberships.html +2 -2
- package/docs/module-UserNotifications.html +2 -2
- package/docs/module-UserProfile.html +2 -2
- package/docs/progress-row_method-card.js.html +3 -2
- package/docs/railcontent.js.html +14 -137
- package/docs/sanity.js.html +2 -2
- package/docs/userActivity.js.html +85 -150
- package/docs/user_account.ts.html +2 -2
- package/docs/user_chat.js.html +2 -2
- package/docs/user_interests.js.html +2 -2
- package/docs/user_management.js.html +2 -2
- package/docs/user_memberships.ts.html +2 -2
- package/docs/user_notifications.js.html +2 -2
- package/docs/user_onboarding.ts.html +10 -6
- package/docs/user_payments.ts.html +2 -2
- package/docs/user_permissions.js.html +2 -2
- package/docs/user_profile.js.html +2 -2
- package/docs/user_sessions.js.html +2 -2
- package/docs/user_types.js.html +2 -2
- package/docs/user_user-management-system.js.html +2 -2
- package/package.json +11 -3
- package/src/contentTypeConfig.js +6 -0
- package/src/index.d.ts +7 -31
- package/src/index.js +10 -34
- package/src/services/content-org/learning-paths.ts +31 -0
- package/src/services/contentAggregator.js +2 -2
- package/src/services/contentLikes.js +6 -39
- package/src/services/contentProgress.js +181 -479
- package/src/services/dataContext.js +0 -2
- package/src/services/progress-row/method-card.js +1 -0
- package/src/services/railcontent.js +12 -135
- package/src/services/sentry/.indexignore +0 -0
- package/src/services/sentry/index.ts +23 -0
- package/src/services/sync/.indexignore +0 -0
- package/src/services/sync/adapters/factory.ts +26 -0
- package/src/services/sync/adapters/lokijs.ts +1 -0
- package/src/services/sync/adapters/sqlite.ts +1 -0
- package/src/services/sync/concurrency-safety.ts +4 -0
- package/src/services/sync/context/index.ts +43 -0
- package/src/services/sync/context/providers/base.ts +4 -0
- package/src/services/sync/context/providers/connectivity.ts +14 -0
- package/src/services/sync/context/providers/durability.ts +5 -0
- package/src/services/sync/context/providers/index.ts +5 -0
- package/src/services/sync/context/providers/session.ts +8 -0
- package/src/services/sync/context/providers/tabs.ts +18 -0
- package/src/services/sync/context/providers/visibility.ts +14 -0
- package/src/services/sync/database/factory.ts +10 -0
- package/src/services/sync/errors/boundary.ts +45 -0
- package/src/services/sync/errors/index.ts +49 -0
- package/src/services/sync/fetch.ts +310 -0
- package/src/services/sync/index.ts +80 -0
- package/src/services/sync/manager.ts +139 -0
- package/src/services/sync/models/Base.ts +47 -0
- package/src/services/sync/models/ContentLike.ts +16 -0
- package/src/services/sync/models/ContentProgress.ts +69 -0
- package/src/services/sync/models/Practice.ts +72 -0
- package/src/services/sync/models/PracticeDayNote.ts +23 -0
- package/src/services/sync/models/index.ts +4 -0
- package/src/services/sync/repositories/base.ts +247 -0
- package/src/services/sync/repositories/content-likes.ts +26 -0
- package/src/services/sync/repositories/content-progress.ts +160 -0
- package/src/services/sync/repositories/index.ts +4 -0
- package/src/services/sync/repositories/practice-day-notes.ts +4 -0
- package/src/services/sync/repositories/practices.ts +52 -0
- package/src/services/sync/repository-proxy.ts +48 -0
- package/src/services/sync/resolver.ts +84 -0
- package/src/services/sync/retry.ts +88 -0
- package/src/services/sync/run-scope.ts +30 -0
- package/src/services/sync/schema/index.ts +66 -0
- package/src/services/sync/serializers/index.ts +2 -0
- package/src/services/sync/serializers/model.ts +32 -0
- package/src/services/sync/serializers/raw.ts +21 -0
- package/src/services/sync/store/index.ts +779 -0
- package/src/services/sync/store/push-coalescer.ts +57 -0
- package/src/services/sync/store-configs.ts +41 -0
- package/src/services/sync/strategies/base.ts +21 -0
- package/src/services/sync/strategies/index.ts +12 -0
- package/src/services/sync/strategies/initial.ts +11 -0
- package/src/services/sync/strategies/polling.ts +54 -0
- package/src/services/sync/telemetry/index.ts +140 -0
- package/src/services/sync/telemetry/sampling.ts +91 -0
- package/src/services/sync/utils/event-emitter.ts +24 -0
- package/src/services/sync/utils/index.ts +1 -0
- package/src/services/sync/utils/throttle.ts +93 -0
- package/src/services/sync/utils/timers.ts +9 -0
- package/src/services/userActivity.js +83 -148
- package/test/contentProgress.test.js +6 -39
- package/test/live/contentProgressLive.test.js +2 -31
- package/tools/generate-index.cjs +10 -4
- package/.claude/settings.local.json +0 -8
- package/babel.config.cjs +0 -3
|
@@ -8,8 +8,6 @@ import { globalConfig } from './config.js'
|
|
|
8
8
|
const excludeFromGeneratedIndex = []
|
|
9
9
|
|
|
10
10
|
//These constants need to match MWP UserDataVersionKeyEnum enum
|
|
11
|
-
export const ContentLikesVersionKey = 0
|
|
12
|
-
export const ContentProgressVersionKey = 1
|
|
13
11
|
export const UserActivityVersionKey = 2
|
|
14
12
|
export const PollingStateVersionKey = 3
|
|
15
13
|
|
|
@@ -10,13 +10,6 @@ import { fetchJSONHandler } from '../lib/httpHelper.js'
|
|
|
10
10
|
* @type {string[]}
|
|
11
11
|
*/
|
|
12
12
|
const excludeFromGeneratedIndex = [
|
|
13
|
-
'fetchUserLikes',
|
|
14
|
-
'postContentLiked',
|
|
15
|
-
'postContentUnliked',
|
|
16
|
-
'postRecordWatchSession',
|
|
17
|
-
'postContentStarted',
|
|
18
|
-
'postContentComplete',
|
|
19
|
-
'postContentReset',
|
|
20
13
|
'fetchUserPermissionsData',
|
|
21
14
|
]
|
|
22
15
|
|
|
@@ -275,10 +268,6 @@ export async function fetchUserPermissionsData() {
|
|
|
275
268
|
return (await fetchHandler(url, 'get')) ?? []
|
|
276
269
|
}
|
|
277
270
|
|
|
278
|
-
async function fetchDataHandler(url, dataVersion, method = 'get') {
|
|
279
|
-
return fetchHandler(url, method, dataVersion)
|
|
280
|
-
}
|
|
281
|
-
|
|
282
271
|
async function postDataHandler(url, data) {
|
|
283
272
|
return fetchHandler(url, 'post', null, data)
|
|
284
273
|
}
|
|
@@ -297,27 +286,7 @@ async function deleteDataHandler(url, data) {
|
|
|
297
286
|
|
|
298
287
|
export async function fetchLikeCount(contendId) {
|
|
299
288
|
const url = `/api/content/v1/content/like_count/${contendId}`
|
|
300
|
-
return await
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
export async function fetchUserLikes(currentVersion) {
|
|
304
|
-
let url = `/api/content/v1/user/likes`
|
|
305
|
-
return fetchDataHandler(url, currentVersion)
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
export async function postContentLiked(contentId) {
|
|
309
|
-
let url = `/api/content/v1/user/likes/${contentId}`
|
|
310
|
-
return await postDataHandler(url)
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
export async function postContentUnliked(contentId) {
|
|
314
|
-
let url = `/api/content/v1/user/likes/${contentId}`
|
|
315
|
-
return await deleteDataHandler(url)
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
export async function fetchContentProgress(currentVersion) {
|
|
319
|
-
let url = `/content/user/progress/all`
|
|
320
|
-
return fetchDataHandler(url, currentVersion)
|
|
289
|
+
return await fetchHandler(url)
|
|
321
290
|
}
|
|
322
291
|
|
|
323
292
|
export async function postPlaylistContentEngaged(playlistItemId) {
|
|
@@ -325,25 +294,6 @@ export async function postPlaylistContentEngaged(playlistItemId) {
|
|
|
325
294
|
return postDataHandler(url)
|
|
326
295
|
}
|
|
327
296
|
|
|
328
|
-
export async function postRecordWatchSession(
|
|
329
|
-
contentId,
|
|
330
|
-
mediaTypeId,
|
|
331
|
-
mediaLengthSeconds,
|
|
332
|
-
currentSeconds,
|
|
333
|
-
secondsPlayed,
|
|
334
|
-
sessionId
|
|
335
|
-
) {
|
|
336
|
-
let url = `/railtracker/v2/media-playback-session`
|
|
337
|
-
return postDataHandler(url, {
|
|
338
|
-
content_id: contentId,
|
|
339
|
-
media_type_id: mediaTypeId,
|
|
340
|
-
media_length_seconds: mediaLengthSeconds,
|
|
341
|
-
current_second: currentSeconds,
|
|
342
|
-
seconds_played: secondsPlayed,
|
|
343
|
-
session_id: sessionId,
|
|
344
|
-
})
|
|
345
|
-
}
|
|
346
|
-
|
|
347
297
|
/**
|
|
348
298
|
* Fetch the user's best award for this challenge
|
|
349
299
|
*
|
|
@@ -378,58 +328,6 @@ export async function fetchUserBadges(brand = null) {
|
|
|
378
328
|
return await fetchHandler(url, 'get')
|
|
379
329
|
}
|
|
380
330
|
|
|
381
|
-
/**
|
|
382
|
-
* complete a content's progress for a given user
|
|
383
|
-
* @param contentId
|
|
384
|
-
* @param collection {object|null} - the collection context of the progress. null is normal content progress
|
|
385
|
-
* @param collection.type - the type of collection. options: ["learning-path"]
|
|
386
|
-
* @param collection.id - the content_id of collection.
|
|
387
|
-
* @returns {Promise<any|string|null>}
|
|
388
|
-
*/
|
|
389
|
-
export async function postContentComplete(contentId, collection = null) {
|
|
390
|
-
let url = `/api/content/v1/user/progress/complete/${contentId}`
|
|
391
|
-
const body = {collection: collection}
|
|
392
|
-
return postDataHandler(url, body)
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* start the user's progress on a content
|
|
397
|
-
* @param contentId
|
|
398
|
-
* @param collection {object|null} - the collection context of the progress. null is normal content progress
|
|
399
|
-
* @param collection.type - the type of collection. options: ["learning-path"]
|
|
400
|
-
* @param collection.id - the content_id of collection.
|
|
401
|
-
* @returns {Promise<any|string|null>}
|
|
402
|
-
*/
|
|
403
|
-
export async function postContentStart(contentId, collection = null) {
|
|
404
|
-
let url = `/api/content/v1/user/progress/start/${contentId}`
|
|
405
|
-
const body = {collection: collection}
|
|
406
|
-
return postDataHandler(url, body)
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* resets the user's progress on a content
|
|
411
|
-
* @param contentId
|
|
412
|
-
* @param collection {object|null} - the collection context of the progress. null is normal content progress
|
|
413
|
-
* @param collection.type - the type of collection. options: ["learning-path"]
|
|
414
|
-
* @param collection.id - the content_id of collection.
|
|
415
|
-
* @returns {Promise<any|string|null>}
|
|
416
|
-
*/
|
|
417
|
-
export async function postContentReset(contentId, collection = null) {
|
|
418
|
-
let url = `/api/content/v1/user/progress/reset/${contentId}`
|
|
419
|
-
const body = {collection: collection}
|
|
420
|
-
return postDataHandler(url, body)
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* restores the user's progress on a content
|
|
425
|
-
* @param contentId
|
|
426
|
-
* @returns {Promise<any|string|null>}
|
|
427
|
-
*/
|
|
428
|
-
export async function postContentRestore(contentId) {
|
|
429
|
-
let url = `/api/content/v1/user/progress/restore/${contentId}`
|
|
430
|
-
return postDataHandler(url)
|
|
431
|
-
}
|
|
432
|
-
|
|
433
331
|
/**
|
|
434
332
|
* Set a user's StudentView Flag
|
|
435
333
|
*
|
|
@@ -624,24 +522,21 @@ export async function fetchComment(commentId) {
|
|
|
624
522
|
return comment.parent ? comment.parent : comment
|
|
625
523
|
}
|
|
626
524
|
|
|
627
|
-
export async function fetchUserPractices(
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
const
|
|
631
|
-
const url = `/api/user/practices/v1/practices${query}`
|
|
632
|
-
const response = await fetchDataHandler(url, currentVersion)
|
|
633
|
-
const { data, version } = response
|
|
525
|
+
export async function fetchUserPractices(userId) {
|
|
526
|
+
const url = `/api/user/practices/v1/practices?user_id=${userId}`
|
|
527
|
+
const response = await fetchHandler(url)
|
|
528
|
+
const { data } = response
|
|
634
529
|
const userPractices = data
|
|
635
530
|
if (!userPractices) {
|
|
636
|
-
return {
|
|
531
|
+
return {}
|
|
637
532
|
}
|
|
638
533
|
|
|
639
534
|
const formattedPractices = userPractices.reduce((acc, practice) => {
|
|
640
|
-
if (!acc[practice.
|
|
641
|
-
acc[practice.
|
|
535
|
+
if (!acc[practice.date]) {
|
|
536
|
+
acc[practice.date] = []
|
|
642
537
|
}
|
|
643
538
|
|
|
644
|
-
acc[practice.
|
|
539
|
+
acc[practice.date].push({
|
|
645
540
|
id: practice.id,
|
|
646
541
|
duration_seconds: practice.duration_seconds,
|
|
647
542
|
})
|
|
@@ -649,29 +544,11 @@ export async function fetchUserPractices(currentVersion = 0, { userId } = {}) {
|
|
|
649
544
|
return acc
|
|
650
545
|
}, {})
|
|
651
546
|
|
|
652
|
-
return
|
|
653
|
-
data: {
|
|
654
|
-
practices: formattedPractices,
|
|
655
|
-
},
|
|
656
|
-
version,
|
|
657
|
-
}
|
|
547
|
+
return formattedPractices
|
|
658
548
|
}
|
|
659
549
|
|
|
660
|
-
export async function
|
|
661
|
-
const url = `/api/user/practices/v1/practices`
|
|
662
|
-
return await fetchHandler(url, 'POST', null, practiceDetails)
|
|
663
|
-
}
|
|
664
|
-
export async function fetchUserPracticeMeta(practiceIds, userId = null) {
|
|
665
|
-
if (practiceIds.length == 0) {
|
|
666
|
-
return []
|
|
667
|
-
}
|
|
668
|
-
const params = new URLSearchParams()
|
|
669
|
-
practiceIds.forEach((id) => params.append('practice_ids[]', id))
|
|
670
|
-
|
|
671
|
-
if (userId !== null) {
|
|
672
|
-
params.append('user_id', userId)
|
|
673
|
-
}
|
|
674
|
-
const url = `/api/user/practices/v1/practices?${params.toString()}`
|
|
550
|
+
export async function fetchUserPracticeMeta(day, userId) {
|
|
551
|
+
const url = `/api/user/practices/v1/practices?user_id=${userId}&date=${date}`
|
|
675
552
|
return await fetchHandler(url, 'GET', null)
|
|
676
553
|
}
|
|
677
554
|
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as Sentry from "@sentry/browser";
|
|
2
|
+
|
|
3
|
+
type SentryBrowserInitOptions = NonNullable<Parameters<typeof Sentry.init>[0]>;
|
|
4
|
+
|
|
5
|
+
type TracesSampler = SentryBrowserInitOptions['tracesSampler'];
|
|
6
|
+
type BeforeSend = SentryBrowserInitOptions['beforeSend'];
|
|
7
|
+
type BeforeSendTransaction = SentryBrowserInitOptions['beforeSendTransaction'];
|
|
8
|
+
|
|
9
|
+
// Compose multiple handlers of the same type into one.
|
|
10
|
+
// Stops at first handler that returns a non-undefined value.
|
|
11
|
+
|
|
12
|
+
export function composeHandlers<H extends TracesSampler>(...handlers: H[]): H;
|
|
13
|
+
export function composeHandlers<H extends BeforeSend>(...handlers: H[]): H;
|
|
14
|
+
export function composeHandlers<H extends BeforeSendTransaction>(...handlers: H[]): H;
|
|
15
|
+
export function composeHandlers<H extends (...args: any[]) => any>(...handlers: H[]): H {
|
|
16
|
+
return ((...args: Parameters<H>) => {
|
|
17
|
+
for (const handler of handlers) {
|
|
18
|
+
const res = handler(...args);
|
|
19
|
+
if (res !== undefined) return res;
|
|
20
|
+
}
|
|
21
|
+
return args[0]
|
|
22
|
+
}) as H;
|
|
23
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import schema from '../schema'
|
|
2
|
+
|
|
3
|
+
import type SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite'
|
|
4
|
+
import type LokiJSAdapter from '@nozbe/watermelondb/adapters/lokijs'
|
|
5
|
+
|
|
6
|
+
export type DatabaseAdapter = SQLiteAdapter | LokiJSAdapter
|
|
7
|
+
|
|
8
|
+
type SQLiteAdapterOptions = ConstructorParameters<typeof SQLiteAdapter>[0]
|
|
9
|
+
type LokiJSAdapterOptions = ConstructorParameters<typeof LokiJSAdapter>[0]
|
|
10
|
+
|
|
11
|
+
type DatabaseAdapterOptions = SQLiteAdapterOptions & LokiJSAdapterOptions
|
|
12
|
+
|
|
13
|
+
export default function syncAdapterFactory<T extends DatabaseAdapter>(
|
|
14
|
+
AdapterClass: new (options: DatabaseAdapterOptions) => T,
|
|
15
|
+
namespace: string,
|
|
16
|
+
opts: Omit<DatabaseAdapterOptions, 'schema' | 'migrations'>
|
|
17
|
+
): () => T {
|
|
18
|
+
const options = {
|
|
19
|
+
...opts,
|
|
20
|
+
dbName: `sync:${namespace}`,
|
|
21
|
+
schema,
|
|
22
|
+
migrations: undefined
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return () => new AdapterClass(options)
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@nozbe/watermelondb/adapters/lokijs'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@nozbe/watermelondb/adapters/sqlite'
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BaseSessionProvider,
|
|
3
|
+
BaseConnectivityProvider,
|
|
4
|
+
BaseVisibilityProvider,
|
|
5
|
+
BaseTabsProvider,
|
|
6
|
+
BaseDurabilityProvider,
|
|
7
|
+
} from './providers'
|
|
8
|
+
|
|
9
|
+
type Providers = {
|
|
10
|
+
session: BaseSessionProvider
|
|
11
|
+
connectivity: BaseConnectivityProvider
|
|
12
|
+
visibility: BaseVisibilityProvider
|
|
13
|
+
tabs: BaseTabsProvider
|
|
14
|
+
durability: BaseDurabilityProvider
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default class SyncContext {
|
|
18
|
+
constructor(private providers: Providers) {}
|
|
19
|
+
|
|
20
|
+
start() {
|
|
21
|
+
Object.values(this.providers).forEach((p) => p.start())
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
stop() {
|
|
25
|
+
Object.values(this.providers).forEach((p) => p.stop())
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get session() {
|
|
29
|
+
return this.providers.session
|
|
30
|
+
}
|
|
31
|
+
get connectivity() {
|
|
32
|
+
return this.providers.connectivity
|
|
33
|
+
}
|
|
34
|
+
get visibility() {
|
|
35
|
+
return this.providers.visibility
|
|
36
|
+
}
|
|
37
|
+
get tabs() {
|
|
38
|
+
return this.providers.tabs
|
|
39
|
+
}
|
|
40
|
+
get durability() {
|
|
41
|
+
return this.providers.durability
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import EventEmitter from "../../utils/event-emitter";
|
|
2
|
+
import BaseContextProvider from "./base";
|
|
3
|
+
|
|
4
|
+
export default abstract class BaseConnectivityProvider extends BaseContextProvider {
|
|
5
|
+
private emitter = new EventEmitter<{ change: [boolean] }>()
|
|
6
|
+
|
|
7
|
+
abstract getValue(): boolean
|
|
8
|
+
|
|
9
|
+
subscribe(listener: (value: boolean) => void) {
|
|
10
|
+
return this.emitter.on('change', listener)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
protected notifyListeners = () => this.emitter.emit('change', this.getValue())
|
|
14
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as BaseSessionProvider } from './session'
|
|
2
|
+
export { default as BaseConnectivityProvider } from './connectivity'
|
|
3
|
+
export { default as BaseVisibilityProvider } from './visibility'
|
|
4
|
+
export { default as BaseTabsProvider, NullTabsProvider } from './tabs'
|
|
5
|
+
export { default as BaseDurabilityProvider } from './durability'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import BaseContextProvider from "./base";
|
|
2
|
+
|
|
3
|
+
export default abstract class BaseTabsProvider extends BaseContextProvider {
|
|
4
|
+
abstract hasOtherTabs(): boolean
|
|
5
|
+
abstract broadcast<T>(name: string, payload: T): void
|
|
6
|
+
abstract subscribe<T>(name: string, callback: (payload: T) => void): () => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class NullTabsProvider extends BaseTabsProvider {
|
|
10
|
+
hasOtherTabs() {
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
broadcast() {}
|
|
15
|
+
subscribe() {
|
|
16
|
+
return () => {}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import EventEmitter from "../../utils/event-emitter";
|
|
2
|
+
import BaseContextProvider from "./base";
|
|
3
|
+
|
|
4
|
+
export default abstract class BaseVisibilityProvider extends BaseContextProvider {
|
|
5
|
+
private emitter = new EventEmitter<{ change: [boolean] }>()
|
|
6
|
+
|
|
7
|
+
abstract getValue(): boolean
|
|
8
|
+
|
|
9
|
+
subscribe(listener: (value: boolean) => void) {
|
|
10
|
+
return this.emitter.on('change', listener)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
protected notifyListeners = () => this.emitter.emit('change', this.getValue())
|
|
14
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DatabaseAdapter } from '../adapters/factory'
|
|
2
|
+
import { Database, } from '@nozbe/watermelondb'
|
|
3
|
+
import * as modelClasses from '../models'
|
|
4
|
+
|
|
5
|
+
export default function syncDatabaseFactory(adapter: () => DatabaseAdapter) {
|
|
6
|
+
return () => new Database({
|
|
7
|
+
adapter: adapter(),
|
|
8
|
+
modelClasses: Object.values(modelClasses)
|
|
9
|
+
})
|
|
10
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { SyncTelemetry } from "../telemetry/index";
|
|
2
|
+
import { SyncError, SyncUnexpectedError } from "./index";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Safely executes a function within a "sync boundary" — ensuring that
|
|
6
|
+
* any thrown or rejected errors (even from asynchronous code) are caught,
|
|
7
|
+
* wrapped, and reported via SyncManager telemetry.
|
|
8
|
+
*
|
|
9
|
+
* This is especially useful for code that runs "out of band" — meaning
|
|
10
|
+
* it's not directly part of the main sync pipeline, and errors might
|
|
11
|
+
* otherwise not be decorated/reported how we like (like in an generic
|
|
12
|
+
* app-wide global error handler).
|
|
13
|
+
*
|
|
14
|
+
* - Automatically catches both synchronous and asynchronous errors.
|
|
15
|
+
* - Wraps unknown errors in `SyncUnexpectedError`, including optional `context`.
|
|
16
|
+
* - Reports all handled errors through `SyncManager.telemetry.capture`.
|
|
17
|
+
*
|
|
18
|
+
* @param fn The function to run inside the error boundary.
|
|
19
|
+
* @param context Optional contextual details to include in captured errors.
|
|
20
|
+
* @returns The result of `fn`, or a promise that resolves to it.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export function inBoundary<T, TContext extends Record<string, any>>(fn: (context: TContext) => T, context?: TContext): T;
|
|
24
|
+
export function inBoundary<T, TContext extends Record<string, any>>(fn: (context: TContext) => Promise<T>, context?: TContext): Promise<T>;
|
|
25
|
+
export function inBoundary<T, TContext extends Record<string, any>>(fn: (context: TContext) => T | Promise<T>, context?: TContext): T | Promise<T> {
|
|
26
|
+
try {
|
|
27
|
+
const result = fn(context || ({} as TContext));
|
|
28
|
+
|
|
29
|
+
if (result instanceof Promise) {
|
|
30
|
+
return result.catch((err: unknown) => {
|
|
31
|
+
const wrapped = err instanceof SyncError ? err : new SyncUnexpectedError((err as Error).message, context);
|
|
32
|
+
SyncTelemetry.getInstance().capture(wrapped)
|
|
33
|
+
|
|
34
|
+
throw wrapped;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return result;
|
|
39
|
+
} catch (err: unknown) {
|
|
40
|
+
const wrapped = err instanceof SyncError ? err : new SyncUnexpectedError((err as Error).message, context);
|
|
41
|
+
SyncTelemetry.getInstance().capture(wrapped);
|
|
42
|
+
|
|
43
|
+
throw wrapped;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import SyncStore from "../store"
|
|
2
|
+
|
|
3
|
+
type ErrorDetails = Record<string, unknown>
|
|
4
|
+
type Without<TRecord, T extends string> = {
|
|
5
|
+
[K in T]?: never
|
|
6
|
+
} & TRecord
|
|
7
|
+
|
|
8
|
+
export class SyncError extends Error {
|
|
9
|
+
private _reported = false
|
|
10
|
+
readonly details?: ErrorDetails
|
|
11
|
+
|
|
12
|
+
constructor(message: string, details?: ErrorDetails) {
|
|
13
|
+
super(message)
|
|
14
|
+
this.name = 'SyncError'
|
|
15
|
+
Object.setPrototypeOf(this, new.target.prototype)
|
|
16
|
+
|
|
17
|
+
this.details = details
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
markReported() {
|
|
21
|
+
this._reported = true
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
isReported() {
|
|
25
|
+
return this._reported
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getDetails() {
|
|
29
|
+
return this.details
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class SyncStoreError extends SyncError {
|
|
34
|
+
constructor(message: string, store: SyncStore, details?: Without<ErrorDetails, 'store'>) {
|
|
35
|
+
super(message, { ...details, store })
|
|
36
|
+
this.name = 'SyncStoreError'
|
|
37
|
+
Object.setPrototypeOf(this, new.target.prototype)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// useful for transforming non-sync-related errors into one
|
|
42
|
+
// that captures surrounding details (e.g., table name)
|
|
43
|
+
export class SyncUnexpectedError extends SyncError {
|
|
44
|
+
constructor(message: string, details?: ErrorDetails) {
|
|
45
|
+
super(message, details)
|
|
46
|
+
this.name = 'SyncUnexpectedError'
|
|
47
|
+
Object.setPrototypeOf(this, new.target.prototype)
|
|
48
|
+
}
|
|
49
|
+
}
|