10
10
import {
11
11
Ack ,
12
12
AddUserActivityRequest ,
13
+ AddUserExternalAssociationRequest ,
13
14
AdvanceExternalPipelineIngestionProgress ,
15
+ CreateEdgeArgs ,
14
16
CreateNodeArgs ,
17
+ EdgeUtil ,
15
18
Eid ,
16
19
GetNodeSliceArgs ,
17
20
NewNodeResponse ,
18
21
Nid ,
19
22
NodeBatch ,
20
23
NodeBatchRequestBody ,
21
24
NodeCreatedVia ,
25
+ NodeEdges ,
22
26
NodePatchRequest ,
23
27
NodeUtil ,
24
28
OriginId ,
25
29
StorageApi ,
30
+ TEdge ,
26
31
TEdgeJson ,
27
32
TNode ,
28
33
TNodeJson ,
@@ -89,6 +94,12 @@ type Lav =
89
94
| OriginToActivityLav
90
95
| ExtPipelineLav
91
96
97
+ type YekLav = { yek : Yek ; lav : Lav }
98
+
99
+ function isOfArrayKind ( lav : Lav ) : lav is OriginToNidLav | NidToEdgeLav {
100
+ return Array . isArray ( lav . lav . value )
101
+ }
102
+
92
103
// TODO[snikitin@outlook .com] Describe that the purpose of this wrapper is to
93
104
// add a bit of ORM-like typesafety to browser.Storage.StorageArea.
94
105
// Without this it's very difficult to keep track of what the code is doing
@@ -100,7 +111,7 @@ class YekLavStore {
100
111
this . store = store
101
112
}
102
113
103
- set ( items : { yek : Yek ; lav : Lav } [ ] ) : Promise < void > {
114
+ set ( items : YekLav [ ] ) : Promise < void > {
104
115
for ( const item of items ) {
105
116
if ( item . yek . yek . kind !== item . lav . lav . kind ) {
106
117
throw new Error (
@@ -125,6 +136,7 @@ class YekLavStore {
125
136
get ( yek : NidYek [ ] ) : Promise < NidLav [ ] >
126
137
get ( yek : OriginToActivityYek ) : Promise < OriginToActivityLav | undefined >
127
138
get ( yek : ExtPipelineYek ) : Promise < ExtPipelineLav | undefined >
139
+ get ( yek : Yek ) : Promise < Lav | undefined >
128
140
get ( yek : Yek | Yek [ ] ) : Promise < Lav | Lav [ ] | undefined > {
129
141
if ( Array . isArray ( yek ) ) {
130
142
const keys : string [ ] = yek . map ( ( value : Yek ) => this . stringify ( value ) )
@@ -150,6 +162,49 @@ class YekLavStore {
150
162
)
151
163
}
152
164
165
+ // TODO[snikitin@outlook .com] Explain that this method is a poor man's attempt
166
+ // to increase atomicity of data insertion
167
+ async prepareAppend (
168
+ yek : OriginToNidYek ,
169
+ lav : OriginToNidLav
170
+ ) : Promise < {
171
+ yek : OriginToNidYek
172
+ lav : OriginToNidLav
173
+ } >
174
+ async prepareAppend (
175
+ yek : NidToEdgeYek ,
176
+ lav : NidToEdgeLav
177
+ ) : Promise < {
178
+ yek : NidToEdgeYek
179
+ lav : NidToEdgeLav
180
+ } >
181
+ async prepareAppend ( yek : Yek , appended_lav : Lav ) : Promise < YekLav > {
182
+ if ( yek . yek . kind !== appended_lav . lav . kind ) {
183
+ throw new Error (
184
+ `Attempted to append a key/value pair of mismatching kinds: '${ yek . yek . kind } ' !== '${ appended_lav . lav . kind } '`
185
+ )
186
+ }
187
+ const lav = await this . get ( yek )
188
+ if ( lav != null && ! isOfArrayKind ( lav ) ) {
189
+ throw new Error ( `prepareAppend only works/makes sense for arrays` )
190
+ }
191
+ const value = lav ?. lav . value ?? [ ]
192
+ // TODO[snikitin@outlook .com] I'm sure it's possible to convince Typescript
193
+ // that below is safe, but don't know how
194
+ return {
195
+ yek,
196
+ // @ts -ignore Type '{...}' is not assignable to type 'Lav'
197
+ lav : {
198
+ lav : {
199
+ kind : yek . yek . kind ,
200
+ // @ts -ignore Each member of the union type has signatures, but none of those
201
+ // signatures are compatible with each other
202
+ value : value . concat ( appended_lav . lav . value ) ,
203
+ } ,
204
+ } ,
205
+ }
206
+ }
207
+
153
208
private stringify ( yek : Yek ) : string {
154
209
switch ( yek . yek . kind ) {
155
210
case 'nid' :
@@ -221,11 +276,10 @@ async function createNode(
221
276
const yek : OriginToNidYek = {
222
277
yek : { kind : 'origin->nid' , key : args . origin } ,
223
278
}
224
- let lav : OriginToNidLav | undefined = await store . get ( yek )
225
- lav = lav ?? { lav : { kind : 'origin->nid' , value : [ ] } }
226
- const nidsWithThisOrigin : Nid [ ] = lav . lav . value
227
- nidsWithThisOrigin . push ( node . nid )
228
- records . push ( { yek, lav } )
279
+ const lav : OriginToNidLav = {
280
+ lav : { kind : 'origin->nid' , value : [ node . nid ] } ,
281
+ }
282
+ records . push ( await store . prepareAppend ( yek , lav ) )
229
283
}
230
284
231
285
if ( from_nid . length > 0 || to_nid . length > 0 ) {
@@ -316,6 +370,67 @@ async function updateNode(
316
370
return { ack : true }
317
371
}
318
372
373
+ async function createEdge (
374
+ store : YekLavStore ,
375
+ args : CreateEdgeArgs
376
+ ) : Promise < TEdge > {
377
+ // TODO[snikitin@outlook .com] Evaluate if ownership support is needed
378
+ // and implement if yes
379
+ const owned_by = 'todo'
380
+
381
+ const createdAt : number = unixtime . now ( )
382
+ const edge : TEdgeJson = {
383
+ eid : generateEid ( ) ,
384
+ from_nid : args . from ,
385
+ to_nid : args . to ,
386
+ crtd : createdAt ,
387
+ upd : createdAt ,
388
+ is_sticky : false ,
389
+ owned_by,
390
+ }
391
+
392
+ const items : YekLav [ ] = [ ]
393
+ {
394
+ const yek : NidToEdgeYek = { yek : { kind : 'nid->edge' , key : args . from } }
395
+ const lav : NidToEdgeLav = { lav : { kind : 'nid->edge' , value : [ edge ] } }
396
+ items . push ( await store . prepareAppend ( yek , lav ) )
397
+ }
398
+ {
399
+ const reverseEdge : TEdgeJson = {
400
+ ...edge ,
401
+ from_nid : args . to ,
402
+ to_nid : args . from ,
403
+ }
404
+ const yek : NidToEdgeYek = { yek : { kind : 'nid->edge' , key : args . to } }
405
+ const lav : NidToEdgeLav = {
406
+ lav : { kind : 'nid->edge' , value : [ reverseEdge ] } ,
407
+ }
408
+ items . push ( await store . prepareAppend ( yek , lav ) )
409
+ }
410
+ await store . set ( items )
411
+ return EdgeUtil . fromJson ( edge )
412
+ }
413
+
414
+ async function getNodeAllEdges (
415
+ store : YekLavStore ,
416
+ nid : string
417
+ ) : Promise < NodeEdges > {
418
+ const yek : NidToEdgeYek = { yek : { kind : 'nid->edge' , key : nid } }
419
+ const lav : NidToEdgeLav | undefined = await store . get ( yek )
420
+ const ret : NodeEdges = { from_edges : [ ] , to_edges : [ ] }
421
+ if ( lav == null ) {
422
+ return ret
423
+ }
424
+ for ( const edge of lav . lav . value ) {
425
+ if ( edge . to_nid === nid ) {
426
+ ret . from_edges . push ( EdgeUtil . fromJson ( edge ) )
427
+ } else {
428
+ ret . to_edges . push ( EdgeUtil . fromJson ( edge ) )
429
+ }
430
+ }
431
+ return ret
432
+ }
433
+
319
434
async function addExternalUserActivity (
320
435
store : YekLavStore ,
321
436
origin : OriginId ,
@@ -461,10 +576,10 @@ export function makeLocalStorageApi(
461
576
} ,
462
577
} ,
463
578
edge : {
464
- // create: createEdge,
465
- // get: getNodeAllEdges,
466
- // sticky: switchEdgeStickiness ,
467
- // delete: deleteEdge ,
579
+ create : ( args : CreateEdgeArgs ) => createEdge ( store , args ) ,
580
+ get : ( nid : string , _signal ?: AbortSignal ) => getNodeAllEdges ( store , nid ) ,
581
+ sticky : throwUnimplementedError ( 'edge.sticky' ) ,
582
+ delete : throwUnimplementedError ( 'edge.delete' ) ,
468
583
} ,
469
584
activity : {
470
585
external : {
@@ -477,8 +592,21 @@ export function makeLocalStorageApi(
477
592
getExternalUserActivity ( store , origin ) ,
478
593
} ,
479
594
association : {
480
- // record: recordExternalAssociation,
481
- // get: getExternalAssociation,
595
+ // TODO[snikitin@outlook .com] Replace stubs with real implementation
596
+ record : (
597
+ _origin : {
598
+ from : OriginId
599
+ to : OriginId
600
+ } ,
601
+ _body : AddUserExternalAssociationRequest ,
602
+ _signal ?: AbortSignal
603
+ ) => Promise . resolve ( { ack : true } ) ,
604
+ get : (
605
+ { } : {
606
+ origin : OriginId
607
+ } ,
608
+ _signal ?: AbortSignal
609
+ ) => Promise . resolve ( { from : [ ] , to : [ ] } ) ,
482
610
} ,
483
611
} ,
484
612
external : {
0 commit comments