@@ -49,6 +49,10 @@ import type {
49
49
ResourceAttention ,
50
50
NodeDeleteArgs ,
51
51
NodeCreatedVia ,
52
+ UserExternalAssociationType ,
53
+ GetUserExternalAssociationsResponse ,
54
+ OriginHash ,
55
+ ExternalAssociationEnd ,
52
56
} from 'smuggler-api'
53
57
import {
54
58
INodeIterator ,
@@ -149,6 +153,28 @@ type ExtPipelineToNidYek = GenericYek<
149
153
>
150
154
type ExtPipelineToNidLav = GenericLav < 'ext-pipe-id->nid' , { nid : Nid } [ ] >
151
155
156
+ type OriginToExtAssociationYek = GenericYek < 'origin->ext-assoc' , OriginId >
157
+ type OriginToExtAssociationValue = {
158
+ /**
159
+ * `direction`'s meaning may be unintuitive when it comes to writing/reading code.
160
+ * Instead, at the time of this writing the intent was to optimise for
161
+ * a different purpose - debugging of 'browser.storage.local' data when it
162
+ * gets dumped to log/console.
163
+ *
164
+ * 'origin->ext-assoc' yek/lav pair looks somewhat like
165
+ * {yek:*origin-id-1*}/{lav: *direction*, *origin-id-2*}
166
+ * which is expected to be read by a developer as:
167
+ * " *origin-id-1* has association *from / to* *origin-id-2*"
168
+ */
169
+ direction : 'from' | 'to'
170
+ other : OriginId
171
+ association : UserExternalAssociationType
172
+ }
173
+ type OriginToExtAssociationLav = GenericLav <
174
+ 'origin->ext-assoc' ,
175
+ OriginToExtAssociationValue [ ]
176
+ >
177
+
152
178
type Yek =
153
179
| AllNidsYek
154
180
| NidToNodeYek
@@ -157,6 +183,7 @@ type Yek =
157
183
| OriginToActivityYek
158
184
| ExtPipelineYek
159
185
| ExtPipelineToNidYek
186
+ | OriginToExtAssociationYek
160
187
type Lav =
161
188
| AllNidsLav
162
189
| NidToNodeLav
@@ -165,12 +192,18 @@ type Lav =
165
192
| OriginToActivityLav
166
193
| ExtPipelineLav
167
194
| ExtPipelineToNidLav
195
+ | OriginToExtAssociationLav
168
196
169
197
type YekLav = { yek : Yek ; lav : Lav }
170
198
171
199
function isOfArrayKind (
172
200
lav : Lav
173
- ) : lav is OriginToNidLav | NidToEdgeLav | AllNidsLav | ExtPipelineToNidLav {
201
+ ) : lav is
202
+ | OriginToNidLav
203
+ | NidToEdgeLav
204
+ | AllNidsLav
205
+ | ExtPipelineToNidLav
206
+ | OriginToExtAssociationLav {
174
207
return Array . isArray ( lav . lav . value )
175
208
}
176
209
@@ -211,21 +244,36 @@ class YekLavStore {
211
244
get ( yek : AllNidsYek ) : Promise < AllNidsLav | undefined >
212
245
get ( yek : NidToNodeYek ) : Promise < NidToNodeLav | undefined >
213
246
get ( yek : OriginToNidYek ) : Promise < OriginToNidLav | undefined >
247
+ get (
248
+ yek : OriginToNidYek [ ]
249
+ ) : Promise < { yek : OriginToNidYek ; lav : OriginToNidLav } [ ] >
214
250
get ( yek : NidToEdgeYek ) : Promise < NidToEdgeLav | undefined >
215
- get ( yek : NidToNodeYek [ ] ) : Promise < NidToNodeLav [ ] >
251
+ get ( yek : NidToNodeYek [ ] ) : Promise < { yek : NidToNodeYek ; lav : NidToNodeLav } [ ] >
216
252
get ( yek : OriginToActivityYek ) : Promise < OriginToActivityLav | undefined >
217
253
get ( yek : ExtPipelineYek ) : Promise < ExtPipelineLav | undefined >
218
254
get ( yek : ExtPipelineToNidYek ) : Promise < ExtPipelineToNidLav | undefined >
255
+ get (
256
+ yek : OriginToExtAssociationYek
257
+ ) : Promise < OriginToExtAssociationLav | undefined >
219
258
get ( yek : Yek ) : Promise < Lav | undefined >
220
- get ( yek : Yek | Yek [ ] ) : Promise < Lav | Lav [ ] | undefined > {
259
+ get ( yek : Yek | Yek [ ] ) : Promise < Lav | YekLav [ ] | undefined > {
221
260
if ( Array . isArray ( yek ) ) {
222
- const keys : string [ ] = yek . map ( ( value : Yek ) => this . stringify ( value ) )
261
+ const keyToYek = new Map < string , Yek > ( )
262
+ yek . forEach ( ( singleYek : Yek ) =>
263
+ keyToYek . set ( this . stringify ( singleYek ) , singleYek )
264
+ )
265
+ const keys : string [ ] = Array . from ( keyToYek . keys ( ) )
223
266
const records : Promise < Record < string , any > > = this . store . get ( keys )
224
- return records . then ( ( records : Record < string , any > ) : Promise < Lav [ ] > => {
225
- const lavs : Lav [ ] = Object . keys ( records ) . map (
226
- ( key : string ) => records [ key ] as Lav
227
- )
228
- return Promise . resolve ( lavs )
267
+ return records . then ( ( records : Record < string , any > ) : Promise < YekLav [ ] > => {
268
+ const yeklavs : YekLav [ ] = [ ]
269
+ for ( const [ key , yek ] of keyToYek ) {
270
+ const lav = key in records ? ( records [ key ] as Lav ) : undefined
271
+ if ( lav == null ) {
272
+ continue
273
+ }
274
+ yeklavs . push ( { yek, lav } )
275
+ }
276
+ return Promise . resolve ( yeklavs )
229
277
} )
230
278
}
231
279
@@ -408,6 +456,8 @@ class YekLavStore {
408
456
return 'ext-pipe:' + yek . yek . key . pipeline_key
409
457
case 'ext-pipe-id->nid' :
410
458
return 'ext-pipe-id->nid:' + yek . yek . key . pipeline_key
459
+ case 'origin->ext-assoc' :
460
+ return 'origin->ext-assoc:' + yek . yek . key . id
411
461
}
412
462
}
413
463
}
@@ -581,7 +631,9 @@ async function getNodesByOrigin(
581
631
return { yek : { kind : 'nid->node' , key : nid } }
582
632
}
583
633
)
584
- const nidLavs : NidToNodeLav [ ] = await store . get ( nidYeks )
634
+ const nidLavs : NidToNodeLav [ ] = ( await store . get ( nidYeks ) ) . map (
635
+ ( yeklav ) => yeklav . lav
636
+ )
585
637
return nidLavs . map ( ( lav : NidToNodeLav ) => NodeUtil . fromJson ( lav . lav . value ) )
586
638
}
587
639
@@ -592,7 +644,9 @@ async function getNodeBatch(
592
644
const yeks : NidToNodeYek [ ] = args . nids . map ( ( nid : Nid ) : NidToNodeYek => {
593
645
return { yek : { kind : 'nid->node' , key : nid } }
594
646
} )
595
- const lavs : NidToNodeLav [ ] = await store . get ( yeks )
647
+ const lavs : NidToNodeLav [ ] = ( await store . get ( yeks ) ) . map (
648
+ ( yeklav ) => yeklav . lav
649
+ )
596
650
return {
597
651
nodes : lavs . map ( ( lav : NidToNodeLav ) => NodeUtil . fromJson ( lav . lav . value ) ) ,
598
652
}
@@ -930,7 +984,7 @@ async function advanceUserIngestionProgress(
930
984
return { ack : true }
931
985
}
932
986
933
- export async function getAllNids (
987
+ async function getAllNids (
934
988
store : YekLavStore ,
935
989
_args : NodeGetAllNidsArgs
936
990
) : Promise < Nid [ ] > {
@@ -946,6 +1000,124 @@ export async function getAllNids(
946
1000
return nids
947
1001
}
948
1002
1003
+ async function recordAssociation (
1004
+ store : YekLavStore ,
1005
+ args : ActivityAssociationRecordArgs
1006
+ ) : Promise < Ack > {
1007
+ const { from, to } = args . origin
1008
+ const yek : OriginToExtAssociationYek = {
1009
+ yek : { kind : 'origin->ext-assoc' , key : from } ,
1010
+ }
1011
+ const reverseYek : OriginToExtAssociationYek = {
1012
+ yek : { kind : 'origin->ext-assoc' , key : to } ,
1013
+ }
1014
+ const orEmpty = (
1015
+ input : OriginToExtAssociationLav | undefined
1016
+ ) : OriginToExtAssociationLav => {
1017
+ return input != null
1018
+ ? input
1019
+ : { lav : { kind : 'origin->ext-assoc' , value : [ ] } }
1020
+ }
1021
+ const [ lav , rlav ] : OriginToExtAssociationLav [ ] = await Promise . all ( [
1022
+ store . get ( yek ) . then ( orEmpty ) ,
1023
+ store . get ( reverseYek ) . then ( orEmpty ) ,
1024
+ ] )
1025
+
1026
+ if ( ! lav . lav . value . every ( ( assoc ) => assoc . other . id !== to . id ) ) {
1027
+ throw new Error ( `${ from } -> ${ to } association already exists` )
1028
+ }
1029
+ if ( ! rlav . lav . value . every ( ( assoc ) => assoc . other . id !== from . id ) ) {
1030
+ throw new Error (
1031
+ `${ from } -> ${ to } association does not exist, but its reverse version does. Data is unexpectedly inconsistent.`
1032
+ )
1033
+ }
1034
+
1035
+ lav . lav . value . push ( {
1036
+ direction : 'to' ,
1037
+ other : to ,
1038
+ association : args . body . association ,
1039
+ } )
1040
+ rlav . lav . value . push ( {
1041
+ direction : 'from' ,
1042
+ other : from ,
1043
+ association : args . body . association ,
1044
+ } )
1045
+ await store . set ( [
1046
+ { yek, lav } ,
1047
+ { yek : reverseYek , lav : rlav } ,
1048
+ ] )
1049
+ return { ack : true }
1050
+ }
1051
+
1052
+ async function getAssociations (
1053
+ store : YekLavStore ,
1054
+ args : ActivityAssociationGetArgs
1055
+ ) : Promise < GetUserExternalAssociationsResponse > {
1056
+ let value : OriginToExtAssociationValue [ ] = [ ]
1057
+ // Step 1: get all association records
1058
+ {
1059
+ const yek : OriginToExtAssociationYek = {
1060
+ yek : { kind : 'origin->ext-assoc' , key : args . origin } ,
1061
+ }
1062
+ const lav : OriginToExtAssociationLav | undefined = await store . get ( yek )
1063
+ value = lav ?. lav . value ?? [ ]
1064
+ }
1065
+ if ( value . length === 0 ) {
1066
+ return { from : [ ] , to : [ ] }
1067
+ }
1068
+ const originToNids = new Map < OriginHash , Nid [ ] > ( )
1069
+ // Step 2: get nids of all origins found in association records
1070
+ {
1071
+ const yeks : OriginToNidYek [ ] = value . map (
1072
+ ( v : OriginToExtAssociationValue ) => {
1073
+ return {
1074
+ yek : { kind : 'origin->nid' , key : v . other } ,
1075
+ }
1076
+ }
1077
+ )
1078
+ yeks . push ( { yek : { kind : 'origin->nid' , key : args . origin } } )
1079
+ const yeklavs : {
1080
+ yek : OriginToNidYek
1081
+ lav : OriginToNidLav
1082
+ } [ ] = await store . get ( yeks )
1083
+ for ( const { yek, lav } of yeklavs ) {
1084
+ originToNids . set (
1085
+ yek . yek . key . id ,
1086
+ lav . lav . value . map ( ( { nid } ) => nid )
1087
+ )
1088
+ }
1089
+ }
1090
+ const ret : GetUserExternalAssociationsResponse = { from : [ ] , to : [ ] }
1091
+ const makeAssociationEnd = (
1092
+ origin_hash : OriginHash
1093
+ ) : ExternalAssociationEnd => {
1094
+ return { origin_hash, nids : originToNids . get ( origin_hash ) ?? [ ] }
1095
+ }
1096
+
1097
+ // Step 3: enrich association records with nids connected to their origins
1098
+ value . forEach ( ( association ) => {
1099
+ switch ( association . direction ) {
1100
+ case 'from' : {
1101
+ ret . from . push ( {
1102
+ from : makeAssociationEnd ( association . other . id ) ,
1103
+ to : makeAssociationEnd ( args . origin . id ) ,
1104
+ association : association . association ,
1105
+ } )
1106
+ return
1107
+ }
1108
+ case 'to' : {
1109
+ ret . to . push ( {
1110
+ from : makeAssociationEnd ( args . origin . id ) ,
1111
+ to : makeAssociationEnd ( association . other . id ) ,
1112
+ association : association . association ,
1113
+ } )
1114
+ return
1115
+ }
1116
+ }
1117
+ } )
1118
+ return ret
1119
+ }
1120
+
949
1121
export function makeBrowserExtStorageApi (
950
1122
browserStore : browser . Storage . StorageArea ,
951
1123
account : UserAccount
@@ -1008,11 +1180,9 @@ export function makeBrowserExtStorageApi(
1008
1180
getExternalUserActivity ( store , args ) ,
1009
1181
} ,
1010
1182
association : {
1011
- // TODO[snikitin@outlook .com] Replace stubs with real implementation
1012
- record : ( _args : ActivityAssociationRecordArgs ) =>
1013
- Promise . resolve ( { ack : true } ) ,
1014
- get : ( _args : ActivityAssociationGetArgs ) =>
1015
- Promise . resolve ( { from : [ ] , to : [ ] } ) ,
1183
+ record : ( args : ActivityAssociationRecordArgs ) =>
1184
+ recordAssociation ( store , args ) ,
1185
+ get : ( args : ActivityAssociationGetArgs ) => getAssociations ( store , args ) ,
1016
1186
} ,
1017
1187
} ,
1018
1188
external : {
0 commit comments