1+ import { MultiMap } from 'util/multimap' ;
2+ import { Timestamps } from 'util/timestamps' ;
3+
4+ import { Identity } from '../../identity' ;
5+ import { Hash , HashedObject , MutableObject , MutationOp , MutableContentEvents , ClassRegistry } from '../../model' ;
6+ import { RefUpdateOp } from '../mutable/MutableReference' ;
7+
8+ import { Authorizer } from '../../model/causal/Authorization' ;
9+ import { Verification } from '../../model/causal/Authorization' ;
10+
11+ import { AuthError , BaseCausalCollection , CausalCollectionConfig } from './CausalCollection' ;
12+
13+
14+ type UpdateSig = {
15+ opHash : Hash ,
16+ sequence : number ,
17+ timestamp : string
18+ } ;
19+
20+ function sig ( op : CausalRefUpdateOp < any > ) {
21+ return { opHash : op . getLastHash ( ) , sequence : op . sequence as number , timestamp : op . timestamp as string } ;
22+ }
23+
24+ // We want an array with the "latest" update (by causality, then timestmap, then hash) at the end
25+
26+ // the following should return -1 if u2 comes after u1;
27+
28+ function compareUpdateSigs ( u1 : UpdateSig , u2 : UpdateSig ) {
29+ if ( u2 . sequence > u1 . sequence ) {
30+ return - 1 ;
31+ } else if ( u1 . sequence < u2 . sequence ) {
32+ return 1 ;
33+ } else { // u2.sequence === u1.sequence
34+ if ( Timestamps . after ( u2 . timestamp , u1 . timestamp ) ) {
35+ return - 1 ;
36+ } else if ( Timestamps . after ( u1 . timestamp , u2 . timestamp ) ) {
37+ return 1 ;
38+ } else { // u2.timestamp === u1.timestamp
39+ return u1 . opHash . localeCompare ( u2 . opHash ) ;
40+ }
41+ }
42+ }
43+
44+ class CausalReference < T > extends BaseCausalCollection < T > {
45+
46+ static className = 'hhs/v0/CausalReference' ;
47+
48+ // all the applied update ops, the latest that is valid is the current value.
49+ _causallyOrderedUpdates : Array < UpdateSig > ;
50+
51+ _latestValidIdx ?: number ; // <- we cache the idx of the latest valid op in the array,
52+ _value ?: T ; // <- and its value, if any.
53+
54+ _largestSequence ?: number ;
55+
56+ constructor ( config ?: CausalCollectionConfig ) {
57+ super ( [ CausalRefUpdateOp . className ] , config ) ;
58+
59+ this . setRandomId ( ) ;
60+
61+ this . _causallyOrderedUpdates = [ ] ;
62+ }
63+
64+ getValue ( ) : T | undefined {
65+ return this . _value ;
66+ }
67+
68+ async setValue ( value : T , author ?: Identity ) : Promise < void > {
69+
70+ if ( ! ( value instanceof HashedObject ) && ! HashedObject . isLiteral ( value ) ) {
71+ throw new Error ( 'CausalReferences can contain either a class deriving from HashedObject or a pure literal (a constant, without any HashedObjects within).' ) ;
72+ }
73+
74+ if ( ! this . shouldAcceptElement ( value ) ) {
75+ throw new Error ( 'CausalReference has type/element contraints that reject the element that is being added:' + value )
76+ }
77+
78+ const nextSeq = this . _largestSequence === undefined ? 0 : this . _largestSequence + 1 ;
79+
80+ let op = new CausalRefUpdateOp < T > ( this , value , nextSeq , author ) ;
81+
82+ const auth = this . createUpdateAuthorizer ( author ) ;
83+
84+ this . setCurrentPrevOpsTo ( op ) ;
85+
86+ if ( ! ( await auth . attempt ( op ) ) ) {
87+ throw new AuthError ( 'Cannot authorize addition operation on CausalReference ' + this . hash ( ) + ', author is: ' + author ?. hash ( ) ) ;
88+ }
89+
90+ return this . applyNewOp ( op ) ;
91+ }
92+
93+ protected createUpdateAuthorizer ( author ?: Identity ) : Authorizer {
94+ return this . createWriteAuthorizer ( author ) ;
95+ }
96+
97+ async canSetValue ( _value ?: T , author ?: Identity ) : Promise < boolean > {
98+ return this . createUpdateAuthorizer ( author ) . attempt ( ) ;
99+ }
100+
101+ async mutate ( op : MutationOp , valid : boolean ) : Promise < boolean > {
102+ let refUpdateOp = op as CausalRefUpdateOp < T > ;
103+
104+
105+ let mutated = false ;
106+
107+ if ( op instanceof RefUpdateOp ) {
108+
109+ const up = sig ( op ) ;
110+
111+ let idx : number ; // the position of op in the array
112+
113+ if ( ! this . _allAppliedOps . has ( up . opHash ) ) {
114+
115+ // if the op has not been applied, we find the right place for it:
116+ const length = this . _causallyOrderedUpdates . length
117+ idx = length ;
118+
119+ while ( idx > 0 && compareUpdateSigs ( this . _causallyOrderedUpdates [ idx - 1 ] , up ) > 0 ) {
120+ idx = idx - 1 ;
121+ }
122+
123+ // and then insert it there:
124+ this . _causallyOrderedUpdates . splice ( idx , 0 , up ) ;
125+ } else {
126+
127+ // else, we just go through the array until we find it
128+ idx = length - 1 ;
129+
130+ while ( idx > 0 && this . _causallyOrderedUpdates [ idx - 1 ] . opHash !== up . opHash ) {
131+ idx = idx - 1 ;
132+ }
133+ }
134+
135+ let newValueIdx : number | undefined ;
136+ let newValueOp : CausalRefUpdateOp < T > | undefined ;
137+
138+ let unsetValue = false ; // to indicate that there are no valid ops left,
139+ // and that the value should be set back to undefined
140+
141+ if ( valid ) {
142+ if ( this . _latestValidIdx === undefined || idx > this . _latestValidIdx ) {
143+ // we need to set a new value!
144+ newValueIdx = idx ;
145+ newValueOp = op ;
146+ }
147+ } else {
148+ if ( this . _latestValidIdx === idx ) {
149+ // the current value has been invalidated, look for the next-best
150+
151+ let nextValueIdx = length ;
152+
153+ while ( nextValueIdx >= 0 && ! this . isValidOp ( this . _causallyOrderedUpdates [ nextValueIdx ] . opHash ) ) {
154+ nextValueIdx = nextValueIdx - 1 ;
155+ }
156+
157+ if ( nextValueIdx >= 0 ) {
158+ newValueIdx = nextValueIdx ;
159+ newValueOp = await this . loadOp ( this . _causallyOrderedUpdates [ nextValueIdx ] . opHash ) as CausalRefUpdateOp < T > ;
160+ } else {
161+ if ( this . _value !== undefined ) {
162+ unsetValue = true ;
163+ }
164+ }
165+ }
166+ }
167+
168+ mutated = unsetValue || newValueIdx !== undefined ;
169+
170+ const oldValue = this . _value ;
171+
172+ if ( unsetValue ) {
173+ this . _latestValidIdx = undefined ;
174+ this . _value = undefined ;
175+ this . _largestSequence = undefined ;
176+ } else if ( newValueIdx !== undefined ) {
177+ this . _latestValidIdx = newValueIdx ;
178+ this . _value = newValueOp ?. value ;
179+ this . _largestSequence = newValueOp ?. sequence ;
180+ }
181+
182+ if ( mutated ) {
183+ this . _mutationEventSource ?. emit ( { emitter : this , action : 'update' , data : refUpdateOp . getValue ( ) } ) ;
184+
185+ if ( oldValue !== this . _value ) {
186+ if ( oldValue instanceof HashedObject ) {
187+ this . _mutationEventSource ?. emit ( { emitter : this , action : MutableContentEvents . RemoveObject , data : oldValue } ) ;
188+ }
189+ if ( this . _value instanceof HashedObject ) {
190+ this . _mutationEventSource ?. emit ( { emitter : this , action : MutableContentEvents . AddObject , data : this . _value } ) ;
191+ }
192+ }
193+ }
194+
195+ }
196+
197+ return Promise . resolve ( mutated ) ;
198+ }
199+
200+ getMutableContents ( ) : MultiMap < Hash , HashedObject > {
201+ const contents = new MultiMap < Hash , HashedObject > ( ) ;
202+
203+ if ( this . _value instanceof HashedObject ) {
204+ contents . add ( this . _value . hash ( ) , this . _value ) ;
205+ }
206+
207+ return contents ;
208+ }
209+
210+ getMutableContentByHash ( hash : Hash ) : Set < HashedObject > {
211+
212+ const found = new Set < HashedObject > ( ) ;
213+
214+ if ( this . _value instanceof HashedObject && this . _value . hash ( ) === hash ) {
215+ found . add ( this . _value ) ;
216+ }
217+
218+ return found ;
219+ }
220+
221+ getClassName ( ) : string {
222+ return CausalReference . className ;
223+ }
224+
225+ init ( ) : void {
226+
227+ }
228+
229+ async validate ( references : Map < Hash , HashedObject > ) {
230+
231+ if ( ! ( await super . validate ( references ) ) ) {
232+ return false ;
233+ }
234+
235+ return true ;
236+ }
237+
238+ shouldAcceptMutationOp ( op : MutationOp , opReferences : Map < Hash , HashedObject > ) : boolean {
239+
240+ if ( ! super . shouldAcceptMutationOp ( op , opReferences ) ) {
241+ return false ;
242+ }
243+
244+ if ( op instanceof CausalRefUpdateOp ) {
245+
246+ if ( ! this . shouldAcceptElement ( op . value as T ) ) {
247+ return false ;
248+ }
249+
250+ const author = op . getAuthor ( ) ;
251+
252+ const auth = this . createUpdateAuthorizer ( author ) ;
253+
254+ const usedKeys = new Set < string > ( ) ;
255+
256+ if ( ! auth . verify ( op , usedKeys ) ) {
257+ return false ;
258+ }
259+
260+ if ( ! Verification . checkKeys ( usedKeys , op ) ) {
261+ return false ;
262+ }
263+
264+ }
265+
266+ return true ;
267+ }
268+ }
269+
270+ class CausalRefUpdateOp < T > extends MutationOp {
271+
272+ static className = 'hhs/v0/CausalRefUpdateOp' ;
273+
274+ sequence ?: number ;
275+ timestamp ?: string ;
276+ value ?: T ;
277+
278+
279+ constructor ( targetObject ?: CausalReference < T > , value ?: T , sequence ?: number , author ?: Identity ) {
280+ super ( targetObject ) ;
281+
282+ if ( targetObject !== undefined ) {
283+ this . value = value ;
284+ this . sequence = sequence ;
285+ this . timestamp = Timestamps . uniqueTimestamp ( ) ;
286+
287+ if ( author !== undefined ) {
288+ this . setAuthor ( author ) ;
289+ }
290+ }
291+
292+ }
293+
294+ getClassName ( ) : string {
295+ return CausalRefUpdateOp . className ;
296+ }
297+
298+ init ( ) : void {
299+
300+ }
301+
302+ async validate ( references : Map < Hash , HashedObject > ) {
303+
304+ if ( ! await super . validate ( references ) ) {
305+ return false ;
306+ }
307+
308+ const targetObject = this . getTargetObject ( ) ;
309+
310+ if ( ! ( targetObject instanceof CausalReference ) ) {
311+ return false ;
312+ }
313+
314+ if ( this . sequence === undefined ) {
315+ MutableObject . validationLog . debug ( 'The field sequence is mandatory in class RefUpdateOp' ) ;
316+ return false ;
317+ }
318+
319+ if ( ( typeof this . sequence ) !== 'number' ) {
320+ MutableObject . validationLog . debug ( 'The field sequence should be of type number in class RefUpdateop' ) ;
321+ return false ;
322+ }
323+
324+ if ( this . timestamp === undefined ) {
325+ MutableObject . validationLog . debug ( 'The field timestamp is mandatory in class RefUpdateOp' ) ;
326+ return false ;
327+ }
328+
329+ if ( ( typeof this . timestamp ) !== 'string' ) {
330+ MutableObject . validationLog . debug ( 'The field timestamp should be of type timestamp in class RefUpdateop' ) ;
331+ return false ;
332+ }
333+
334+ if ( this . value === undefined ) {
335+ MutableObject . validationLog . debug ( 'The field value is mandatory in class REfUpdateop' ) ;
336+ return false ;
337+ }
338+
339+ if ( targetObject . acceptedElementHashes !== undefined && ! targetObject . acceptedElementHashes . has ( HashedObject . hashElement ( this . value ) ) ) {
340+ return false ;
341+ }
342+
343+ if ( targetObject . acceptedTypes !== undefined &&
344+ ! (
345+ ( this . value instanceof HashedObject && targetObject . acceptedTypes . has ( this . value . getClassName ( ) ) )
346+ ||
347+ ( ! ( this . value instanceof HashedObject ) && targetObject . acceptedTypes . has ( typeof ( this . value ) ) )
348+ )
349+
350+ ) {
351+
352+ return false ;
353+
354+ }
355+
356+ return true ;
357+ }
358+
359+ getSequence ( ) {
360+ return this . sequence as number ;
361+ }
362+
363+ getTimestamp ( ) {
364+ return this . timestamp as string ;
365+ }
366+
367+ getValue ( ) {
368+ return this . value as T ;
369+ }
370+ }
371+
372+ ClassRegistry . register ( CausalReference . className , CausalReference ) ;
373+ ClassRegistry . register ( CausalRefUpdateOp . className , CausalRefUpdateOp ) ;
374+
375+ export { CausalReference } ;
0 commit comments