@vue-skuilder/db 0.1.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.
Files changed (62) hide show
  1. package/README.md +26 -0
  2. package/dist/core/index.d.mts +3 -0
  3. package/dist/core/index.d.ts +3 -0
  4. package/dist/core/index.js +7906 -0
  5. package/dist/core/index.js.map +1 -0
  6. package/dist/core/index.mjs +7886 -0
  7. package/dist/core/index.mjs.map +1 -0
  8. package/dist/index-QMtzQI65.d.mts +734 -0
  9. package/dist/index-QMtzQI65.d.ts +734 -0
  10. package/dist/index.d.mts +133 -0
  11. package/dist/index.d.ts +133 -0
  12. package/dist/index.js +8726 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/index.mjs +8699 -0
  15. package/dist/index.mjs.map +1 -0
  16. package/eslint.config.mjs +20 -0
  17. package/package.json +47 -0
  18. package/src/core/bulkImport/cardProcessor.ts +165 -0
  19. package/src/core/bulkImport/index.ts +2 -0
  20. package/src/core/bulkImport/types.ts +27 -0
  21. package/src/core/index.ts +9 -0
  22. package/src/core/interfaces/adminDB.ts +27 -0
  23. package/src/core/interfaces/classroomDB.ts +75 -0
  24. package/src/core/interfaces/contentSource.ts +64 -0
  25. package/src/core/interfaces/courseDB.ts +139 -0
  26. package/src/core/interfaces/dataLayerProvider.ts +46 -0
  27. package/src/core/interfaces/index.ts +7 -0
  28. package/src/core/interfaces/navigationStrategyManager.ts +46 -0
  29. package/src/core/interfaces/userDB.ts +183 -0
  30. package/src/core/navigators/elo.ts +76 -0
  31. package/src/core/navigators/index.ts +57 -0
  32. package/src/core/readme.md +9 -0
  33. package/src/core/types/contentNavigationStrategy.ts +21 -0
  34. package/src/core/types/db.ts +7 -0
  35. package/src/core/types/types-legacy.ts +155 -0
  36. package/src/core/types/user.ts +70 -0
  37. package/src/core/util/index.ts +42 -0
  38. package/src/factory.ts +86 -0
  39. package/src/impl/pouch/PouchDataLayerProvider.ts +102 -0
  40. package/src/impl/pouch/adminDB.ts +91 -0
  41. package/src/impl/pouch/auth.ts +48 -0
  42. package/src/impl/pouch/classroomDB.ts +306 -0
  43. package/src/impl/pouch/clientCache.ts +19 -0
  44. package/src/impl/pouch/courseAPI.ts +245 -0
  45. package/src/impl/pouch/courseDB.ts +772 -0
  46. package/src/impl/pouch/courseLookupDB.ts +135 -0
  47. package/src/impl/pouch/index.ts +235 -0
  48. package/src/impl/pouch/pouchdb-setup.ts +16 -0
  49. package/src/impl/pouch/types.ts +7 -0
  50. package/src/impl/pouch/updateQueue.ts +89 -0
  51. package/src/impl/pouch/user-course-relDB.ts +73 -0
  52. package/src/impl/pouch/userDB.ts +1097 -0
  53. package/src/index.ts +8 -0
  54. package/src/study/SessionController.ts +401 -0
  55. package/src/study/SpacedRepetition.ts +128 -0
  56. package/src/study/getCardDataShape.ts +34 -0
  57. package/src/study/index.ts +2 -0
  58. package/src/util/Loggable.ts +11 -0
  59. package/src/util/index.ts +1 -0
  60. package/src/util/logger.ts +55 -0
  61. package/tsconfig.json +12 -0
  62. package/tsup.config.ts +17 -0
@@ -0,0 +1,183 @@
1
+ import {
2
+ ActivityRecord,
3
+ CourseRegistration,
4
+ CourseRegistrationDoc,
5
+ ScheduledCard,
6
+ } from '@/core/types/user';
7
+ import { CourseElo, Status } from '@vue-skuilder/common';
8
+ import { Moment } from 'moment';
9
+ import { CardHistory, CardRecord } from '../types/types-legacy';
10
+ import { UserConfig } from '../types/user';
11
+ import { DocumentUpdater } from '@/study';
12
+
13
+ /**
14
+ * User data and authentication
15
+ */
16
+ export interface UserDBInterface extends DocumentUpdater {
17
+ /**
18
+ * Create a new user account
19
+ */
20
+ createAccount(
21
+ username: string,
22
+ password: string
23
+ ): Promise<{
24
+ status: Status;
25
+ error: string;
26
+ }>;
27
+
28
+ /**
29
+ * Log in as a user
30
+ */
31
+ login(
32
+ username: string,
33
+ password: string
34
+ ): Promise<{
35
+ ok: boolean;
36
+ name?: string;
37
+ roles?: string[];
38
+ }>;
39
+
40
+ /**
41
+ * Log out the current user
42
+ */
43
+ logout(): Promise<{
44
+ ok: boolean;
45
+ }>;
46
+
47
+ getUsername(): string;
48
+
49
+ isLoggedIn(): boolean;
50
+
51
+ /**
52
+ * Get user configuration
53
+ */
54
+ getConfig(): Promise<UserConfig>;
55
+
56
+ /**
57
+ * Update user configuration
58
+ */
59
+ setConfig(config: Partial<UserConfig>): Promise<void>;
60
+
61
+ /**
62
+ * Record a user's interaction with a card
63
+ */
64
+ putCardRecord<T extends CardRecord>(record: T): Promise<CardHistory<CardRecord>>;
65
+
66
+ /**
67
+ * Get cards that the user has seen
68
+ */
69
+ getSeenCards(courseId?: string): Promise<string[]>;
70
+
71
+ /**
72
+ * Get cards that are actively scheduled for review
73
+ */
74
+ getActiveCards(): Promise<string[]>;
75
+
76
+ /**
77
+ * Register user for a course
78
+ */
79
+ registerForCourse(courseId: string, previewMode?: boolean): Promise<PouchDB.Core.Response>;
80
+
81
+ /**
82
+ * Drop a course registration
83
+ */
84
+ dropCourse(courseId: string, dropStatus?: string): Promise<PouchDB.Core.Response>;
85
+
86
+ /**
87
+ * Get user's course registrations
88
+ */
89
+ getCourseRegistrationsDoc(): Promise<CourseRegistrationDoc>;
90
+
91
+ /**
92
+ * Get the registration doc for a specific course.
93
+ * @param courseId
94
+ */
95
+ getCourseRegDoc(courseId: string): Promise<CourseRegistration>;
96
+
97
+ /**
98
+ * Get user's active courses
99
+ */
100
+ getActiveCourses(): Promise<CourseRegistration[]>;
101
+
102
+ /**
103
+ * Get user's pending reviews
104
+ */
105
+ getPendingReviews(courseId?: string): Promise<ScheduledCard[]>;
106
+
107
+ getActivityRecords(): Promise<ActivityRecord[]>;
108
+
109
+ /**
110
+ * Schedule a card for review
111
+ */
112
+ scheduleCardReview(review: {
113
+ user: string;
114
+ course_id: string;
115
+ card_id: string;
116
+ time: Moment;
117
+ scheduledFor: 'course' | 'classroom';
118
+ schedulingAgentId: string;
119
+ }): Promise<void>;
120
+
121
+ /**
122
+ * Remove a scheduled card review
123
+ */
124
+ removeScheduledCardReview(reviewId: string): Promise<void>;
125
+
126
+ /**
127
+ * Register user for a classroom
128
+ */
129
+ registerForClassroom(
130
+ classId: string,
131
+ registerAs: 'student' | 'teacher' | 'aide' | 'admin'
132
+ ): Promise<PouchDB.Core.Response>;
133
+
134
+ /**
135
+ * Drop user from classroom
136
+ */
137
+ dropFromClassroom(classId: string): Promise<PouchDB.Core.Response>;
138
+
139
+ /**
140
+ * Get user's classroom registrations
141
+ */
142
+ getUserClassrooms(): Promise<ClassroomRegistrationDoc>;
143
+
144
+ /**
145
+ * Get user's active classes
146
+ */
147
+ getActiveClasses(): Promise<string[]>;
148
+
149
+ /**
150
+ * Update user's ELO rating for a course
151
+ */
152
+ updateUserElo(courseId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;
153
+
154
+ getCourseInterface(courseId: string): Promise<UsrCrsDataInterface>;
155
+ }
156
+
157
+ export interface UserCourseSettings {
158
+ [setting: string]: string | number | boolean;
159
+ }
160
+
161
+ export interface UserCourseSetting {
162
+ key: string;
163
+ value: string | number | boolean;
164
+ }
165
+
166
+ // [ ] reconsider here. Should maybe be generic type based on <T extends StudyContentSource> ?
167
+ export interface UsrCrsDataInterface {
168
+ getScheduledReviewCount(): Promise<number>;
169
+ getCourseSettings(): Promise<UserCourseSettings>;
170
+ updateCourseSettings(updates: UserCourseSetting[]): void; // [ ] return a result of some sort?
171
+ // getRegistrationDoc(): Promise<CourseRegistration>;
172
+ }
173
+
174
+ export type ClassroomRegistrationDesignation = 'student' | 'teacher' | 'aide' | 'admin';
175
+
176
+ export interface ClassroomRegistration {
177
+ classID: string;
178
+ registeredAs: ClassroomRegistrationDesignation;
179
+ }
180
+
181
+ export interface ClassroomRegistrationDoc {
182
+ registrations: ClassroomRegistration[];
183
+ }
@@ -0,0 +1,76 @@
1
+ import { ScheduledCard } from '../types/user';
2
+ import { CourseDBInterface } from '../interfaces/courseDB';
3
+ import { UserDBInterface } from '../interfaces/userDB';
4
+ import { ContentNavigator } from './index';
5
+ import { CourseElo } from '@vue-skuilder/common';
6
+ import { StudySessionReviewItem, StudySessionNewItem } from '..';
7
+
8
+ export default class ELONavigator extends ContentNavigator {
9
+ user: UserDBInterface;
10
+ course: CourseDBInterface;
11
+
12
+ constructor(
13
+ user: UserDBInterface,
14
+ course: CourseDBInterface
15
+ // The ELO strategy is non-parameterized.
16
+ //
17
+ // It instead relies on existing meta data from the course and user with respect to
18
+ //
19
+ //
20
+ // strategy?: ContentNavigationStrategyData
21
+ ) {
22
+ super();
23
+ this.user = user;
24
+ this.course = course;
25
+ }
26
+
27
+ async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {
28
+ type ratedReview = ScheduledCard & CourseElo;
29
+
30
+ const reviews = await this.user.getPendingReviews(this.course.getCourseID()); // todo: this adds a db round trip - should be server side
31
+ const elo = await this.course.getCardEloData(reviews.map((r) => r.cardId));
32
+
33
+ const ratedReviews = reviews.map((r, i) => {
34
+ const ratedR: ratedReview = {
35
+ ...r,
36
+ ...elo[i],
37
+ };
38
+ return ratedR;
39
+ });
40
+
41
+ ratedReviews.sort((a, b) => {
42
+ return a.global.score - b.global.score;
43
+ });
44
+
45
+ return ratedReviews.map((r) => {
46
+ return {
47
+ ...r,
48
+ contentSourceType: 'course',
49
+ contentSourceID: this.course.getCourseID(),
50
+ cardID: r.cardId,
51
+ courseID: r.courseId,
52
+ qualifiedID: `${r.courseId}-${r.cardId}`,
53
+ reviewID: r._id,
54
+ status: 'review',
55
+ };
56
+ });
57
+ }
58
+
59
+ async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {
60
+ const activeCards = await this.user.getActiveCards();
61
+ return (
62
+ await this.course.getCardsCenteredAtELO({ limit: limit, elo: 'user' }, (c: string) => {
63
+ if (activeCards.some((ac) => c.includes(ac))) {
64
+ return false;
65
+ } else {
66
+ return true;
67
+ }
68
+ })
69
+ ).map((c) => {
70
+ return {
71
+ ...c,
72
+ status: 'new',
73
+ };
74
+ });
75
+ }
76
+ }
@@ -0,0 +1,57 @@
1
+ import {
2
+ StudyContentSource,
3
+ UserDBInterface,
4
+ CourseDBInterface,
5
+ StudySessionReviewItem,
6
+ StudySessionNewItem,
7
+ } from '..';
8
+ import { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';
9
+ import { ScheduledCard } from '../types/user';
10
+ import { logger } from '../../util/logger';
11
+
12
+ export enum Navigators {
13
+ ELO = 'elo',
14
+ }
15
+
16
+ /**
17
+ * A content-navigator provides runtime steering of study sessions.
18
+ */
19
+ export abstract class ContentNavigator implements StudyContentSource {
20
+ /**
21
+ *
22
+ * @param user
23
+ * @param strategyData
24
+ * @returns the runtime object used to steer a study session.
25
+ */
26
+ static async create(
27
+ user: UserDBInterface,
28
+ course: CourseDBInterface,
29
+ strategyData: ContentNavigationStrategyData
30
+ ): Promise<ContentNavigator> {
31
+ const implementingClass = strategyData.implementingClass;
32
+ let NavigatorImpl;
33
+
34
+ // Try different extension variations
35
+ const variations = ['', '.js', '.ts'];
36
+
37
+ for (const ext of variations) {
38
+ try {
39
+ const module = await import(`./${implementingClass}${ext}`);
40
+ NavigatorImpl = module.default;
41
+ break; // Break the loop if loading succeeds
42
+ } catch (e) {
43
+ // Continue to next variation if this one fails
44
+ logger.debug(`Failed to load with extension ${ext}:`, e);
45
+ }
46
+ }
47
+
48
+ if (!NavigatorImpl) {
49
+ throw new Error(`Could not load navigator implementation for: ${implementingClass}`);
50
+ }
51
+
52
+ return new NavigatorImpl(user, course, strategyData);
53
+ }
54
+
55
+ abstract getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;
56
+ abstract getNewCards(n?: number): Promise<StudySessionNewItem[]>;
57
+ }
@@ -0,0 +1,9 @@
1
+ # `db/src/core`
2
+
3
+ Implementation-agnostic type definitions for skuilder data layer.
4
+
5
+ ### Overview
6
+
7
+ - `types` - structured data types
8
+ - `interfaces` - behaviour contracts for reading and writing data
9
+ - `utils` - generic helper functions for working with these data types and interfaces
@@ -0,0 +1,21 @@
1
+ import { DocType, SkuilderCourseData } from './types-legacy';
2
+
3
+ /**
4
+ *
5
+ */
6
+ export interface ContentNavigationStrategyData extends SkuilderCourseData {
7
+ id: string;
8
+ docType: DocType.NAVIGATION_STRATEGY;
9
+ name: string;
10
+ description: string;
11
+ /**
12
+ The name of the class that implements the navigation strategy at runtime.
13
+ */
14
+ implementingClass: string;
15
+
16
+ /**
17
+ A representation of the strategy's parameterization - to be deserialized
18
+ by the implementing class's constructor at runtime.
19
+ */
20
+ serializedData: string;
21
+ }
@@ -0,0 +1,7 @@
1
+ import { Status } from '@vue-skuilder/common';
2
+
3
+ export interface DataLayerResult {
4
+ status: Status;
5
+ message: string;
6
+ id?: string;
7
+ }
@@ -0,0 +1,155 @@
1
+ import { CourseElo, Answer, Evaluation } from '@vue-skuilder/common';
2
+ import { Moment } from 'moment';
3
+ import { logger } from '../../util/logger';
4
+
5
+ export const GuestUsername: string = 'Guest';
6
+
7
+ export const log = (message: string): void => {
8
+ logger.log(message);
9
+ };
10
+
11
+ export enum DocType {
12
+ DISPLAYABLE_DATA = 'DISPLAYABLE_DATA',
13
+ CARD = 'CARD',
14
+ DATASHAPE = 'DATASHAPE',
15
+ QUESTIONTYPE = 'QUESTION',
16
+ VIEW = 'VIEW',
17
+ PEDAGOGY = 'PEDAGOGY',
18
+ CARDRECORD = 'CARDRECORD',
19
+ SCHEDULED_CARD = 'SCHEDULED_CARD',
20
+ TAG = 'TAG',
21
+ NAVIGATION_STRATEGY = 'NAVIGATION_STRATEGY',
22
+ }
23
+
24
+ /**
25
+ * Interface for all data on course content and pedagogy stored
26
+ * in the c/pouch database.
27
+ */
28
+ export interface SkuilderCourseData {
29
+ course: string;
30
+ docType: DocType;
31
+ }
32
+
33
+ export interface Tag extends SkuilderCourseData {
34
+ docType: DocType.TAG;
35
+ name: string;
36
+ snippet: string; // 200 char description of the tag
37
+ wiki: string; // 3000 char md-friendly description
38
+ taggedCards: PouchDB.Core.DocumentId[];
39
+ }
40
+ export interface TagStub {
41
+ name: string;
42
+ snippet: string;
43
+ count: number; // the number of cards that have this tag applied
44
+ }
45
+
46
+ export interface CardData extends SkuilderCourseData {
47
+ docType: DocType.CARD;
48
+ id_displayable_data: PouchDB.Core.DocumentId[];
49
+ id_view: PouchDB.Core.DocumentId;
50
+ elo: CourseElo;
51
+ }
52
+
53
+ /** A list of populated courses in the DB */
54
+ export interface CourseListData extends PouchDB.Core.Response {
55
+ courses: string[];
56
+ }
57
+
58
+ /**
59
+ * The data used to hydrate viewable components (questions, info, etc)
60
+ */
61
+ export interface DisplayableData extends SkuilderCourseData {
62
+ docType: DocType.DISPLAYABLE_DATA;
63
+ author?: string;
64
+ id_datashape: PouchDB.Core.DocumentId;
65
+ data: Field[];
66
+ _attachments?: { [index: string]: PouchDB.Core.FullAttachment };
67
+ }
68
+
69
+ export interface Field {
70
+ data: unknown;
71
+ name: string;
72
+ }
73
+
74
+ export interface DataShapeData extends SkuilderCourseData {
75
+ docType: DocType.DATASHAPE;
76
+ _id: PouchDB.Core.DocumentId;
77
+ questionTypes: PouchDB.Core.DocumentId[];
78
+ }
79
+
80
+ export interface QuestionData extends SkuilderCourseData {
81
+ docType: DocType.QUESTIONTYPE;
82
+ _id: PouchDB.Core.DocumentId;
83
+ viewList: string[];
84
+ dataShapeList: PouchDB.Core.DocumentId[];
85
+ }
86
+
87
+ export const cardHistoryPrefix = 'cardH';
88
+
89
+ export interface CardHistory<T extends CardRecord> {
90
+ _id: PouchDB.Core.DocumentId;
91
+ /**
92
+ * The CouchDB id of the card
93
+ */
94
+ cardID: PouchDB.Core.DocumentId;
95
+
96
+ /**
97
+ * The ID of the course
98
+ */
99
+ courseID: string;
100
+
101
+ /**
102
+ * The to-date largest interval between successful
103
+ * card reviews. `0` indicates no successful reviews.
104
+ */
105
+ bestInterval: number;
106
+
107
+ /**
108
+ * The number of times that a card has been
109
+ * failed in review
110
+ */
111
+ lapses: number;
112
+
113
+ /**
114
+ * The number of consecutive successful impressions
115
+ * on this card
116
+ */
117
+ streak: number;
118
+
119
+ records: T[];
120
+ }
121
+
122
+ export interface CardRecord {
123
+ /**
124
+ * The CouchDB id of the card
125
+ */
126
+ cardID: string;
127
+ /**
128
+ * The ID of the course
129
+ */
130
+ courseID: string;
131
+ /**
132
+ * Number of milliseconds that the user spent before dismissing
133
+ * the card (ie, "I've read this" or "here is my answer")
134
+ *
135
+ * //TODO: this (sometimes?) wants to be replaced with a rich
136
+ * recording of user activity in working the question
137
+ */
138
+ timeSpent: number;
139
+ /**
140
+ * The date-time that the card was rendered. timeStamp + timeSpent will give the
141
+ * time of user submission.
142
+ */
143
+ timeStamp: Moment;
144
+ }
145
+
146
+ export interface QuestionRecord extends CardRecord, Evaluation {
147
+ userAnswer: Answer;
148
+ /**
149
+ * The number of incorrect user submissions prededing this submisstion.
150
+ *
151
+ * eg, if a user is asked 7*6=__, submitting 46, 48, 42 will result in three
152
+ * records being created having 0, 1, and 2 as their recorded 'priorAttempts' values
153
+ */
154
+ priorAttemps: number;
155
+ }
@@ -0,0 +1,70 @@
1
+ import { CourseElo } from '@vue-skuilder/common';
2
+ import { Moment } from 'moment';
3
+
4
+ export interface UserConfig {
5
+ darkMode: boolean;
6
+ likesConfetti: boolean;
7
+ }
8
+
9
+ export interface ActivityRecord {
10
+ timeStamp: number | string;
11
+ [key: string]: any;
12
+ }
13
+
14
+ export interface CourseRegistration {
15
+ status?: 'active' | 'dropped' | 'maintenance-mode' | 'preview';
16
+ courseID: string;
17
+ admin: boolean;
18
+ moderator: boolean;
19
+ user: boolean;
20
+ settings?: {
21
+ [setting: string]: string | number | boolean;
22
+ };
23
+ elo: number | CourseElo;
24
+ }
25
+
26
+ interface StudyWeights {
27
+ [courseID: string]: number;
28
+ }
29
+
30
+ export interface CourseRegistrationDoc {
31
+ courses: CourseRegistration[];
32
+ studyWeight: StudyWeights;
33
+ }
34
+
35
+ export interface ScheduledCard {
36
+ _id: PouchDB.Core.DocumentId;
37
+
38
+ /**
39
+ * The docID of the card to be reviewed
40
+ */
41
+ cardId: PouchDB.Core.DocumentId;
42
+ /**
43
+ * The ID of the course
44
+ */
45
+ courseId: string;
46
+ /**
47
+ * The time at which the card becomes eligible for review.
48
+ *
49
+ * (Should probably be UTC adjusted so that performance is
50
+ * not wonky across time zones)
51
+ */
52
+ reviewTime: Moment;
53
+
54
+ /**
55
+ * The time at which this scheduled event was created.
56
+ */
57
+ scheduledAt: Moment;
58
+
59
+ /**
60
+ * Classifying whether this card is scheduled on behalf of a
61
+ * user-registered course or by as assigned content from a
62
+ * user-registered classroom
63
+ */
64
+ scheduledFor: 'course' | 'classroom';
65
+
66
+ /**
67
+ * The ID of the course or classroom that requested this card
68
+ */
69
+ schedulingAgentId: string;
70
+ }
@@ -0,0 +1,42 @@
1
+ import { cardHistoryPrefix, CardHistory, CardRecord, QuestionRecord } from '../types/types-legacy';
2
+
3
+ export function areQuestionRecords(h: CardHistory<CardRecord>): h is CardHistory<QuestionRecord> {
4
+ return isQuestionRecord(h.records[0]);
5
+ }
6
+
7
+ export function isQuestionRecord(c: CardRecord): c is QuestionRecord {
8
+ return (c as QuestionRecord).userAnswer !== undefined;
9
+ }
10
+
11
+ export function getCardHistoryID(courseID: string, cardID: string): PouchDB.Core.DocumentId {
12
+ return `${cardHistoryPrefix}-${courseID}-${cardID}`;
13
+ }
14
+
15
+ export function parseCardHistoryID(id: string): {
16
+ courseID: string;
17
+ cardID: string;
18
+ } {
19
+ const split = id.split('-');
20
+ let error: string = '';
21
+ error += split.length === 3 ? '' : `\n\tgiven ID has incorrect number of '-' characters`;
22
+ error +=
23
+ split[0] === cardHistoryPrefix ? '' : `\n\tgiven ID does not start with ${cardHistoryPrefix}`;
24
+
25
+ if (split.length === 3 && split[0] === cardHistoryPrefix) {
26
+ return {
27
+ courseID: split[1],
28
+ cardID: split[2],
29
+ };
30
+ } else {
31
+ throw new Error('parseCardHistory Error:' + error);
32
+ }
33
+ }
34
+
35
+ interface PouchDBError extends Error {
36
+ error?: string;
37
+ reason?: string;
38
+ }
39
+
40
+ export function docIsDeleted(e: PouchDBError): boolean {
41
+ return Boolean(e?.error === 'not_found' && e?.reason === 'deleted');
42
+ }