@shaxpir/duiduidui-models 1.3.2 → 1.3.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.
@@ -1,117 +0,0 @@
1
- import { Doc } from '@shaxpir/sharedb/lib/client';
2
- import { CachingHasher, MultiClock } from '@shaxpir/shaxpir-common';
3
- import { ShareSync, ShareSyncFactory } from '../repo';
4
- import { Content, ContentBody, ContentId, ContentMeta, ContentRef } from "./Content";
5
- import { ContentKind } from './ContentKind';
6
- import { MediaCropping } from './Media';
7
- import { BatchOperation } from './Operation';
8
-
9
- export interface ProfilePayload {
10
- username:string;
11
- full_name:string;
12
- avatar_media_ref:ContentId;
13
- avatar_cropping:MediaCropping;
14
- }
15
-
16
- export interface ProfileBody extends ContentBody {
17
- meta:ContentMeta;
18
- payload:ProfilePayload;
19
- }
20
-
21
- export class Profile extends Content {
22
-
23
- public static makeProfileId(userId:ContentId):ContentId {
24
- return CachingHasher.makeMd5Base62Hash(userId + "-" + ContentKind.PROFILE) as ContentId;
25
- }
26
-
27
- public static create(userId:ContentId):Profile {
28
- const now = MultiClock.now();
29
- const profileId = Profile.makeProfileId(userId);
30
- return ShareSyncFactory.get().createContent(
31
- {
32
- meta : {
33
- ref : profileId,
34
- kind : ContentKind.PROFILE,
35
- id : profileId,
36
- owner : userId,
37
- created_at : now,
38
- updated_at : now
39
- },
40
- payload : {
41
- username : null,
42
- full_name : '',
43
- avatar_media_ref : null as ContentRef,
44
- avatar_cropping : null
45
- }
46
- }
47
- ) as Profile;
48
- }
49
-
50
- constructor(doc:Doc, shouldAcquire:boolean, shareSync:ShareSync) {
51
- super(doc, shouldAcquire, shareSync);
52
- }
53
-
54
- public get payload():ProfilePayload {
55
- this.checkDisposed("Profile.payload");
56
- return this.doc.data.payload as ProfilePayload;
57
- }
58
-
59
- public get username():string {
60
- this.checkDisposed("Profile.username");
61
- return this.payload.username;
62
- }
63
- public setUsername(value:string) {
64
- this.checkDisposed("Profile.setUsername");
65
- if (this.username != value) {
66
- const batch = new BatchOperation(this);
67
- batch.setPathValue([ 'payload', 'username' ] , value);
68
- batch.commit();
69
- }
70
- }
71
-
72
- public get fullName():string {
73
- this.checkDisposed("Profile.fullName");
74
- return this.payload.full_name;
75
- }
76
- public setFullName(value:string) {
77
- this.checkDisposed("Profile.setFullName");
78
- if (this.fullName !== value) {
79
- const batch = new BatchOperation(this);
80
- batch.editPathText([ 'payload', 'full_name' ] , value);
81
- batch.commit();
82
- }
83
- }
84
-
85
- public get avatarMediaRef():ContentId {
86
- this.checkDisposed("Profile.avatarMediaRef");
87
- return this.payload.avatar_media_ref;
88
- }
89
- public setAvatarMediaRef(value:ContentId) {
90
- this.checkDisposed("Profile.setAvatarMediaRef");
91
- if (this.avatarMediaRef !== value) {
92
- const batch = new BatchOperation(this);
93
- batch.setPathValue([ 'payload', 'avatar_media_ref' ] , value);
94
- batch.commit();
95
- }
96
- }
97
-
98
- public get avatarCropping():MediaCropping {
99
- this.checkDisposed("Profile.avatarCropping");
100
- return this.payload.avatar_cropping;
101
- }
102
- public setAvatarCropping(value:MediaCropping) {
103
- this.checkDisposed("Profile.setAvatarCropping");
104
- if (this.avatarCropping !== value) {
105
- const batch = new BatchOperation(this);
106
- batch.setPathValue([ 'payload', 'avatar_cropping' ] , value);
107
- batch.commit();
108
- }
109
- }
110
-
111
- public static async findByUsername(username:string):Promise<Profile[]> {
112
- const shareSync = ShareSyncFactory.get();
113
- return shareSync.findAndAcquire(
114
- ContentKind.PROFILE, { "payload.username" : username }
115
- ) as Promise<Profile[]>;
116
- }
117
- }
@@ -1,101 +0,0 @@
1
- import { Doc } from '@shaxpir/sharedb/lib/client';
2
- import { CachingHasher, CompactDateTime, MultiClock } from "@shaxpir/shaxpir-common";
3
- import { ShareSync, ShareSyncFactory } from '../repo';
4
- import { BayesianScore } from './BayesianScore';
5
- import { Content, ContentBody, ContentId, ContentMeta } from "./Content";
6
- import { ContentKind } from './ContentKind';
7
- import { BatchOperation } from './Operation';
8
-
9
- export interface PhraseProgress extends BayesianScore {
10
- text:string;
11
- sense_rank:number;
12
- learn_rank: number;
13
- last_review_utc?: CompactDateTime;
14
- }
15
-
16
- // The Proficiency model represents our assessment of the user's overall language level (user_rank)
17
- // as well as a map of phrases with their learn_rank, alpha, beta, and pr
18
- export interface ProgressPayload {
19
- user_rank:number;
20
- phrases:Record<string, PhraseProgress>;
21
- cognitive_load:number;
22
- }
23
-
24
- export interface ProgressBody extends ContentBody {
25
- meta:ContentMeta;
26
- payload:ProgressPayload;
27
- }
28
-
29
- export class Progress extends Content {
30
-
31
- public static makeProgressId(userId:ContentId):ContentId {
32
- return CachingHasher.makeMd5Base62Hash(userId + "-" + ContentKind.PROGRESS) as ContentId;
33
- }
34
-
35
- public static create(
36
- userId:ContentId
37
- ):Progress {
38
- const now = MultiClock.now();
39
- const progressId = Progress.makeProgressId(userId);
40
- return ShareSyncFactory.get().createContent(
41
- {
42
- meta : {
43
- ref : progressId,
44
- kind : ContentKind.PROGRESS,
45
- id : progressId,
46
- owner : userId,
47
- created_at : now,
48
- updated_at : now
49
- },
50
- payload : {
51
- user_rank : 0,
52
- phrases : {},
53
- cognitive_load : 0
54
- }
55
- }
56
- ) as Progress;
57
- }
58
-
59
- constructor(doc:Doc, shouldAcquire:boolean, shareSync:ShareSync) {
60
- super(doc, shouldAcquire, shareSync);
61
- }
62
-
63
- public get payload():ProgressPayload {
64
- this.checkDisposed("Progress.payload");
65
- return this.doc.data.payload as ProgressPayload;
66
- }
67
-
68
- public setUserRank(value: number): void {
69
- this.checkDisposed("Progress.setUserRank");
70
- if (this.payload.user_rank !== value) {
71
- const batch = new BatchOperation(this);
72
- batch.setPathValue(['payload', 'user_rank'], value);
73
- batch.commit();
74
- }
75
- }
76
-
77
- public setCognitiveLoad(value: number): void {
78
- this.checkDisposed("Progress.setCognitiveLoad");
79
- if (this.payload.cognitive_load !== value) {
80
- const batch = new BatchOperation(this);
81
- batch.setPathValue(['payload', 'cognitive_load'], value);
82
- batch.commit();
83
- }
84
- }
85
-
86
- public updatePhraseProgress(phraseId: string, progress: PhraseProgress): void {
87
- this.checkDisposed("Progress.updatePhraseProgress");
88
- const batch = new BatchOperation(this);
89
- batch.setPathValue(['payload', 'phrases', phraseId], progress);
90
- batch.commit();
91
- }
92
-
93
- public updateMultiplePhrases(updates: Record<string, PhraseProgress>): void {
94
- this.checkDisposed("Progress.updateMultiplePhrases");
95
- const batch = new BatchOperation(this);
96
- for (const [phraseId, progress] of Object.entries(updates)) {
97
- batch.setPathValue(['payload', 'phrases', phraseId], progress);
98
- }
99
- batch.commit();
100
- }
101
- }
@@ -1,18 +0,0 @@
1
- import { MultiTime } from "@shaxpir/shaxpir-common";
2
-
3
- export type ReviewResult = 'FAIL' | 'HARD' | 'GOOD' | 'EASY';
4
-
5
- export interface ReviewLike {
6
- result: ReviewResult;
7
- at: MultiTime;
8
- }
9
-
10
- export interface Review extends ReviewLike {
11
- text: string;
12
- sense_rank: number;
13
- }
14
-
15
- export interface ImpliedReview extends ReviewLike {
16
- context: string;
17
- weight: number;
18
- }
@@ -1,149 +0,0 @@
1
- import { Doc } from '@shaxpir/sharedb/lib/client';
2
- import { CachingHasher, CompactDateTime, MultiClock, MultiTime, Time } from "@shaxpir/shaxpir-common";
3
- import { ShareSync, ShareSyncFactory } from '../repo';
4
- import { Content, ContentBody, ContentId, ContentMeta, ContentRef } from "./Content";
5
- import { ContentKind } from './ContentKind';
6
- import { Metric, MetricName } from './Metric';
7
- import { BatchOperation } from './Operation';
8
- import { Review } from './Review';
9
- import { Workspace } from './Workspace';
10
- import { ArrayView } from './ArrayView';
11
-
12
-
13
- export interface SessionPayload {
14
- start:MultiTime;
15
- end:MultiTime;
16
- tz_offset:number;
17
- device_id:ContentId;
18
- duration_minutes:number;
19
- reviews:Review[];
20
- }
21
-
22
- export interface SessionBody extends ContentBody {
23
- meta:ContentMeta;
24
- payload:SessionPayload;
25
- }
26
-
27
- export class Session extends Content {
28
-
29
- private _reviewsView:ArrayView<Review>;
30
-
31
- constructor(doc:Doc, shouldAcquire:boolean, shareSync:ShareSync) {
32
- super(doc, shouldAcquire, shareSync);
33
- this._reviewsView = new ArrayView(this, [ 'payload', 'reviews' ]);
34
- }
35
-
36
- public static makeSessionId(userId:ContentId):ContentId {
37
- return CachingHasher.makeMd5Base62Hash(userId + "-" + ContentKind.SESSION) as ContentId;
38
- }
39
-
40
- public static makeSessionRef(userId:ContentId, at:MultiTime):ContentRef {
41
- let sessionId = Session.makeSessionId(userId);
42
- return `${sessionId}_${at.utc_time}` as ContentRef;
43
- }
44
-
45
- public static create(
46
- userId:ContentId,
47
- deviceId:ContentId
48
- ):Session {
49
-
50
- let now:MultiTime = MultiClock.now();
51
- let sessionId = Session.makeSessionId(userId);
52
- let sessionRef = Session.makeSessionRef(userId, now);
53
-
54
- return ShareSyncFactory.get().createContent(
55
- {
56
- meta : {
57
- ref : sessionRef,
58
- kind : ContentKind.SESSION,
59
- id : sessionId,
60
- owner : userId,
61
- created_at : now,
62
- updated_at : now
63
- },
64
- payload : {
65
- start : now,
66
- end: now,
67
- tz_offset : (new Date()).getTimezoneOffset(),
68
- duration_minutes : 0,
69
- device_id : deviceId,
70
- reviews : []
71
- }
72
- }
73
- ) as Session;
74
- }
75
-
76
- public get payload():SessionPayload {
77
- this.checkDisposed("Session.payload");
78
- return this.doc.data.payload;
79
- }
80
-
81
- public touch():void {
82
- this.checkDisposed("Session.touch");
83
-
84
- const batch = new BatchOperation(this);
85
-
86
- let nowUtc:CompactDateTime = batch.at.utc_time;
87
- let nowLocal:CompactDateTime = batch.at.local_time;
88
- let minutes:number = Time.absDiffMinutes(nowUtc, this.start.utc_time);
89
-
90
- batch.setPathValue([ 'payload', 'duration_minutes' ] , minutes);
91
- batch.setPathValue([ 'payload', 'end', 'utc_time' ] , nowUtc);
92
- batch.setPathValue([ 'payload', 'end', 'local_time' ] , nowLocal);
93
-
94
- batch.commit();
95
- }
96
-
97
- public get start():MultiTime {
98
- this.checkDisposed("Session.start");
99
- return this.payload.start;
100
- }
101
-
102
- public get end():MultiTime {
103
- this.checkDisposed("Session.end");
104
- return this.payload.end;
105
- }
106
-
107
- public get tzOffset():number {
108
- this.checkDisposed("Session.tzOffset");
109
- return this.payload.tz_offset;
110
- }
111
-
112
- public get durationMinutes():number {
113
- this.checkDisposed("Session.durationMinutes");
114
- return this.payload.duration_minutes;
115
- }
116
-
117
- public get deviceId():ContentId {
118
- this.checkDisposed("Session.deviceId");
119
- return this.payload.device_id;
120
- }
121
-
122
- public async modelUpdated():Promise<void> {
123
- this.checkDisposed("Session.modelUpdated");
124
- await super.modelUpdated();
125
- const shareSync = this.shareSync;
126
- const sessionId = this.id;
127
- const userId = this.owner;
128
- const date = Time.dateFrom(this.createdAt.local_time);
129
- const workspaceId = Workspace.makeWorkspaceId(userId);
130
- const workspace = await this.shareSync.acquire(ContentKind.WORKSPACE, workspaceId) as Workspace;
131
- const sessionsOnDate:Session[] = await workspace.acquireSessionsFromDate(sessionId, date);
132
- workspace.release();
133
- let minutesWritingOnDate:number = 0;
134
- for (let i = 0, len = sessionsOnDate.length; i < len; i++) {
135
- const sessionOnDate = sessionsOnDate[i] as Session;
136
- minutesWritingOnDate += sessionOnDate.durationMinutes;
137
- sessionOnDate.release();
138
- }
139
- const minutesWritingMetricId = Metric.makeMetricId(userId, MetricName.MINUTES_WRITING);
140
- const minutesWritingMetric = await shareSync.acquire(ContentKind.METRIC, minutesWritingMetricId) as Metric;
141
- minutesWritingMetric.setDateAmount(date, minutesWritingOnDate);
142
- minutesWritingMetric.release();
143
- }
144
-
145
- public addReview(review: Review): void {
146
- this.checkDisposed("Session.addReview");
147
- this._reviewsView.push(review);
148
- }
149
- }
@@ -1,202 +0,0 @@
1
- import { Doc } from '@shaxpir/sharedb/lib/client';
2
- import { CachingHasher, CompactDateTime, MultiClock } from "@shaxpir/shaxpir-common";
3
- import { BayesianScore } from './BayesianScore';
4
- import { Content, ContentBody, ContentId, ContentMeta } from "./Content";
5
- import { BuiltInPhrase, Phrase, PhraseExample } from './Phrase';
6
- import { ReviewLike } from "./Review";
7
- import { ContentKind } from './ContentKind';
8
- import { ShareSync, ShareSyncFactory } from '../repo';
9
- import { BatchOperation } from './Operation';
10
- import { ArrayView } from './ArrayView';
11
-
12
- export interface TermPayload extends BayesianScore {
13
-
14
- // These fields are always populated. Note that for non-builtin entries, the sense_rank is always zero
15
- text: string;
16
- sense_rank: number;
17
- learn_rank: number;
18
- phrase_id: number | null;
19
- starred_at: CompactDateTime | null;
20
-
21
- // these phrase fields are omitted for builtin entries (e.g., when phrase_id is not null)
22
- // TODO: what about server-provided entries with numeric 'phrase_id' (e.g., if we split the *huge* local
23
- // db into a smaller local db with all beginner content, and a bigger cloud db with more advanced content)
24
- hanzi_count?: number;
25
- pinyin?: string;
26
- pinyin_tokenized?: string;
27
- transliteration?: string;
28
- translation?: string;
29
- notes?: string;
30
- components?: PhraseExample[];
31
- examples?: PhraseExample[];
32
- keywords?: string[];
33
-
34
- // if this is a builtin entry, these tags overlay the builtin tags
35
- tags: string[];
36
-
37
- reviews: ReviewLike[];
38
- }
39
-
40
- export interface TermBody extends ContentBody {
41
- meta:ContentMeta;
42
- payload:TermPayload;
43
- }
44
-
45
- export class Term extends Content {
46
-
47
- public static makeTermId(userId:ContentId, text:string, senseRank:number):ContentId {
48
- return CachingHasher.makeMd5Base62Hash(`${ContentKind.TERM}-${userId}-${text}-${senseRank}`) as ContentId;
49
- }
50
-
51
- private _reviewsView:ArrayView<ReviewLike>;
52
-
53
- constructor(doc:Doc, shouldAcquire:boolean, shareSync:ShareSync) {
54
- super(doc, shouldAcquire, shareSync);
55
- this._reviewsView = new ArrayView(this, [ 'payload', 'reviews' ]);
56
- }
57
-
58
- public static forUserPhrase(
59
- userId:ContentId,
60
- text:string,
61
- senseRank:number,
62
- learnRank:number,
63
- phraseId?:number
64
- ):Term;
65
- public static forUserPhrase(
66
- userId:ContentId,
67
- phrase:Phrase
68
- ):Term;
69
- public static forUserPhrase(
70
- userId:ContentId,
71
- textOrPhrase:string | Phrase,
72
- senseRank?:number,
73
- learnRank?:number,
74
- phraseId?:number
75
- ):Term {
76
- const now = MultiClock.now();
77
-
78
- // Handle overloaded parameters
79
- let phrase: Phrase;
80
- if (typeof textOrPhrase === 'string') {
81
- // Create a minimal Phrase object from individual parameters
82
- phrase = {
83
- text: textOrPhrase,
84
- sense_rank: senseRank!,
85
- learn_rank: learnRank!,
86
- hanzi_count: textOrPhrase.length,
87
- pinyin: '',
88
- pinyin_tokenized: '',
89
- transliteration: '',
90
- translation: '',
91
- notes: '',
92
- examples: [],
93
- keywords: [],
94
- tags: []
95
- } as Phrase;
96
- } else {
97
- phrase = textOrPhrase;
98
- }
99
-
100
- const termId = CachingHasher.makeMd5Base62Hash(`${userId}-${phrase.text}-${phrase.sense_rank}`) as ContentId;
101
- return ShareSyncFactory.get().createContent(
102
- {
103
- meta : {
104
- ref : termId,
105
- kind : ContentKind.TERM,
106
- id : termId,
107
- owner : userId,
108
- created_at : now,
109
- updated_at : now
110
- },
111
- payload : {
112
-
113
- phrase_id : (typeof textOrPhrase === 'string' && phraseId) ? phraseId : null,
114
- text : phrase.text,
115
- sense_rank : phrase.sense_rank,
116
- hanzi_count : phrase.hanzi_count,
117
- learn_rank : phrase.learn_rank,
118
- pinyin : phrase.pinyin,
119
- pinyin_tokenized : phrase.pinyin_tokenized,
120
- transliteration : phrase.transliteration,
121
- translation : phrase.translation,
122
- notes : phrase.notes,
123
-
124
- examples : phrase.examples,
125
- keywords : phrase.keywords,
126
- tags : phrase.tags,
127
-
128
- starred_at : null,
129
-
130
- alpha: 1,
131
- beta: 1,
132
- theta: 0.5,
133
- reviews: []
134
- }
135
- }
136
- ) as Term;
137
- }
138
-
139
- public static forBuiltinPhrase(
140
- userId:ContentId,
141
- phrase:BuiltInPhrase
142
- ):Term {
143
- const now = MultiClock.now();
144
- const termId = CachingHasher.makeMd5Base62Hash(`${userId}-${phrase.text}-${phrase.sense_rank}`) as ContentId;
145
- return ShareSyncFactory.get().createContent(
146
- {
147
- meta : {
148
- ref : termId,
149
- kind : ContentKind.TERM,
150
- id : termId,
151
- owner : userId,
152
- created_at : now,
153
- updated_at : now
154
- },
155
- payload : {
156
-
157
- text: phrase.text,
158
- sense_rank: phrase.sense_rank,
159
- learn_rank: phrase.learn_rank,
160
- phrase_id : phrase.id,
161
- starred_at: null,
162
-
163
- tags : [],
164
-
165
- alpha: 1,
166
- beta: 1,
167
- theta: 0.5,
168
-
169
- reviews: []
170
- }
171
- }
172
- ) as Term;
173
- }
174
-
175
- public get payload():TermPayload {
176
- this.checkDisposed("Term.payload");
177
- return this.doc.data.payload as TermPayload;
178
- }
179
-
180
- public setStarredAt(value: CompactDateTime | null): void {
181
- this.checkDisposed("Term.setStarredAt");
182
- if (this.payload.starred_at !== value) {
183
- const batch = new BatchOperation(this);
184
- batch.setPathValue(['payload', 'starred_at'], value);
185
- batch.commit();
186
- }
187
- }
188
-
189
- public updateBayesianScore(alpha: number, beta: number, theta: number): void {
190
- this.checkDisposed("Term.updateBayesianScore");
191
- const batch = new BatchOperation(this);
192
- batch.setPathValue(['payload', 'alpha'], alpha);
193
- batch.setPathValue(['payload', 'beta'], beta);
194
- batch.setPathValue(['payload', 'theta'], theta);
195
- batch.commit();
196
- }
197
-
198
- public addReview(review: ReviewLike): void {
199
- this.checkDisposed("Term.addReview");
200
- this._reviewsView.push(review);
201
- }
202
- }
@@ -1,97 +0,0 @@
1
- import { Doc } from '@shaxpir/sharedb/lib/client';
2
- import { CompactDateTime, MultiClock, MultiTime } from '@shaxpir/shaxpir-common';
3
- import { ArrayView } from './ArrayView';
4
- import { Content, ContentId, ContentMeta } from "./Content";
5
- import { ContentKind } from './ContentKind';
6
- import { BatchOperation } from './Operation';
7
- import { ShareSync, ShareSyncFactory } from '../repo';
8
-
9
- export interface VerifiablePhone {
10
- phone:string;
11
- verified_at:CompactDateTime;
12
- }
13
-
14
- export interface VerifiableEmail {
15
- email:string;
16
- verified_at:CompactDateTime;
17
- }
18
-
19
- export interface UserPayload {
20
- emails:VerifiableEmail[];
21
- phones:VerifiablePhone[];
22
- }
23
-
24
- export interface UserBody {
25
- meta:ContentMeta;
26
- payload:UserPayload;
27
- }
28
-
29
- export class User extends Content {
30
-
31
- private _emails:ArrayView<VerifiableEmail>;
32
- private _phones:ArrayView<VerifiablePhone>;
33
-
34
- constructor(doc:Doc, shouldAcquire:boolean, shareSync:ShareSync) {
35
- super(doc, shouldAcquire, shareSync);
36
- this._emails = new ArrayView<VerifiableEmail>(this, [ 'payload', 'emails' ]);
37
- this._phones = new ArrayView<VerifiablePhone>(this, [ 'payload', 'phones' ]);
38
- }
39
-
40
- public get payload():UserPayload {
41
- return this.doc.data.payload as UserPayload;
42
- }
43
-
44
- public static create(
45
- userId:ContentId,
46
- payload:UserPayload,
47
- createdAt?:MultiTime
48
- ):User {
49
- // User creation always happens in UTC time
50
- const now = MultiClock.utcNow();
51
- // If the createdAt time is not provided, use the current time
52
- createdAt ??= now;
53
- return ShareSyncFactory.get().createContent(
54
- {
55
- meta : {
56
- ref : userId,
57
- kind : ContentKind.USER,
58
- id : userId,
59
- owner : userId,
60
- created_at : createdAt,
61
- updated_at : now
62
- },
63
- payload : payload
64
- }
65
- ) as User;
66
- }
67
-
68
- public get emails():ArrayView<VerifiableEmail> {
69
- return this._emails;
70
- }
71
-
72
- public get phones():ArrayView<VerifiablePhone> {
73
- return this._phones;
74
- }
75
-
76
- public verifyEmail(email:string, time:CompactDateTime):void {
77
- const idx = this._emails.firstIndexWhere(e => e.email === email);
78
- if (idx >= 0 && this._emails.get(idx).verified_at !== time) {
79
- const batch = new BatchOperation(this);
80
- batch.setPathValue([ 'payload', 'emails', idx, 'verified_at' ], time);
81
- batch.commit();
82
- } else if (idx < 0) {
83
- this._emails.push({ email, verified_at: time });
84
- }
85
- }
86
-
87
- public verifyPhone(phone:string, time:CompactDateTime):void {
88
- const idx = this._phones.firstIndexWhere(p => p.phone === phone);
89
- if (idx >= 0 && this._phones.get(idx).verified_at !== time) {
90
- const batch = new BatchOperation(this);
91
- batch.setPathValue([ 'payload', 'phones', idx, 'verified_at' ], time);
92
- batch.commit();
93
- } else if (idx < 0) {
94
- this._phones.push({ phone, verified_at: time });
95
- }
96
- }
97
- }