index.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. /*
  2. eslint-disable @typescript-eslint/naming-convention
  3. */
  4. import { EventContext, StoreContext } from '@joystream/hydra-common'
  5. import { Storage } from '../generated/types/storage'
  6. import {
  7. DistributionBucket,
  8. DistributionBucketFamily,
  9. DistributionBucketOperator,
  10. DistributionBucketOperatorMetadata,
  11. DistributionBucketOperatorStatus,
  12. NodeLocationMetadata,
  13. StorageBag,
  14. StorageBucket,
  15. StorageBucketOperatorStatusActive,
  16. StorageBucketOperatorStatusInvited,
  17. StorageBucketOperatorStatusMissing,
  18. StorageDataObject,
  19. StorageSystemParameters,
  20. GeoCoordinates,
  21. } from 'query-node/dist/model'
  22. import BN from 'bn.js'
  23. import { getById, inconsistentState } from '../common'
  24. import {
  25. processDistributionBucketFamilyMetadata,
  26. processDistributionOperatorMetadata,
  27. processStorageOperatorMetadata,
  28. } from './metadata'
  29. import {
  30. createDataObjects,
  31. getStorageSystem,
  32. removeDataObject,
  33. getStorageBucketWithOperatorMetadata,
  34. getBag,
  35. getDynamicBagId,
  36. getDynamicBagOwner,
  37. getDataObjectsInBag,
  38. getDynamicBag,
  39. getDistributionBucketFamilyWithMetadata,
  40. getDistributionBucketOperatorWithMetadata,
  41. distributionBucketId,
  42. distributionOperatorId,
  43. distributionBucketIdByFamilyAndIndex,
  44. } from './utils'
  45. // STORAGE BUCKETS
  46. export async function storage_StorageBucketCreated({ event, store }: EventContext & StoreContext): Promise<void> {
  47. const [
  48. bucketId,
  49. invitedWorkerId,
  50. acceptingNewBags,
  51. dataObjectSizeLimit,
  52. dataObjectCountLimit,
  53. ] = new Storage.StorageBucketCreatedEvent(event).params
  54. const storageBucket = new StorageBucket({
  55. id: bucketId.toString(),
  56. acceptingNewBags: acceptingNewBags.isTrue,
  57. dataObjectCountLimit: new BN(dataObjectCountLimit.toString()),
  58. dataObjectsSizeLimit: new BN(dataObjectSizeLimit.toString()),
  59. dataObjectsCount: new BN(0),
  60. dataObjectsSize: new BN(0),
  61. })
  62. if (invitedWorkerId.isSome) {
  63. const operatorStatus = new StorageBucketOperatorStatusInvited()
  64. operatorStatus.workerId = invitedWorkerId.unwrap().toNumber()
  65. storageBucket.operatorStatus = operatorStatus
  66. } else {
  67. storageBucket.operatorStatus = new StorageBucketOperatorStatusMissing()
  68. }
  69. await store.save<StorageBucket>(storageBucket)
  70. }
  71. export async function storage_StorageOperatorMetadataSet({ event, store }: EventContext & StoreContext): Promise<void> {
  72. const [bucketId, , metadataBytes] = new Storage.StorageOperatorMetadataSetEvent(event).params
  73. const storageBucket = await getStorageBucketWithOperatorMetadata(store, bucketId.toString())
  74. storageBucket.operatorMetadata = await processStorageOperatorMetadata(
  75. event,
  76. store,
  77. storageBucket.operatorMetadata,
  78. metadataBytes
  79. )
  80. await store.save<StorageBucket>(storageBucket)
  81. }
  82. export async function storage_StorageBucketStatusUpdated({ event, store }: EventContext & StoreContext): Promise<void> {
  83. const [bucketId, acceptingNewBags] = new Storage.StorageBucketStatusUpdatedEvent(event).params
  84. const storageBucket = await getById(store, StorageBucket, bucketId.toString())
  85. storageBucket.acceptingNewBags = acceptingNewBags.isTrue
  86. await store.save<StorageBucket>(storageBucket)
  87. }
  88. export async function storage_StorageBucketInvitationAccepted({
  89. event,
  90. store,
  91. }: EventContext & StoreContext): Promise<void> {
  92. const [bucketId, workerId, transactorAccountId] = new Storage.StorageBucketInvitationAcceptedEvent(event).params
  93. const storageBucket = await getById(store, StorageBucket, bucketId.toString())
  94. const operatorStatus = new StorageBucketOperatorStatusActive()
  95. operatorStatus.workerId = workerId.toNumber()
  96. operatorStatus.transactorAccountId = transactorAccountId.toString()
  97. storageBucket.operatorStatus = operatorStatus
  98. await store.save<StorageBucket>(storageBucket)
  99. }
  100. export async function storage_StorageBucketInvitationCancelled({
  101. event,
  102. store,
  103. }: EventContext & StoreContext): Promise<void> {
  104. const [bucketId] = new Storage.StorageBucketInvitationCancelledEvent(event).params
  105. const storageBucket = await getById(store, StorageBucket, bucketId.toString())
  106. const operatorStatus = new StorageBucketOperatorStatusMissing()
  107. storageBucket.operatorStatus = operatorStatus
  108. await store.save<StorageBucket>(storageBucket)
  109. }
  110. export async function storage_StorageBucketOperatorInvited({
  111. event,
  112. store,
  113. }: EventContext & StoreContext): Promise<void> {
  114. const [bucketId, workerId] = new Storage.StorageBucketOperatorInvitedEvent(event).params
  115. const storageBucket = await getById(store, StorageBucket, bucketId.toString())
  116. const operatorStatus = new StorageBucketOperatorStatusInvited()
  117. operatorStatus.workerId = workerId.toNumber()
  118. storageBucket.operatorStatus = operatorStatus
  119. await store.save<StorageBucket>(storageBucket)
  120. }
  121. export async function storage_StorageBucketOperatorRemoved({
  122. event,
  123. store,
  124. }: EventContext & StoreContext): Promise<void> {
  125. const [bucketId] = new Storage.StorageBucketInvitationCancelledEvent(event).params
  126. const storageBucket = await getById(store, StorageBucket, bucketId.toString())
  127. const operatorStatus = new StorageBucketOperatorStatusMissing()
  128. storageBucket.operatorStatus = operatorStatus
  129. await store.save<StorageBucket>(storageBucket)
  130. }
  131. export async function storage_StorageBucketsUpdatedForBag({
  132. event,
  133. store,
  134. }: EventContext & StoreContext): Promise<void> {
  135. const [bagId, addedBucketsSet, removedBucketsSet] = new Storage.StorageBucketsUpdatedForBagEvent(event).params
  136. // Get or create bag
  137. const storageBag = await getBag(store, bagId, ['storageBuckets'])
  138. const removedBucketsIds = Array.from(removedBucketsSet).map((id) => id.toString())
  139. const addedBucketsIds = Array.from(addedBucketsSet).map((id) => id.toString())
  140. storageBag.storageBuckets = (storageBag.storageBuckets || [])
  141. .filter((bucket) => !removedBucketsIds.includes(bucket.id))
  142. .concat(addedBucketsIds.map((id) => new StorageBucket({ id })))
  143. await store.save<StorageBag>(storageBag)
  144. }
  145. export async function storage_VoucherChanged({ event, store }: EventContext & StoreContext): Promise<void> {
  146. const [bucketId, voucher] = new Storage.VoucherChangedEvent(event).params
  147. const bucket = await getById(store, StorageBucket, bucketId.toString())
  148. bucket.dataObjectCountLimit = voucher.objectsLimit
  149. bucket.dataObjectsSizeLimit = voucher.sizeLimit
  150. bucket.dataObjectsCount = voucher.objectsUsed
  151. bucket.dataObjectsSize = voucher.sizeUsed
  152. await store.save<StorageBucket>(bucket)
  153. }
  154. export async function storage_StorageBucketVoucherLimitsSet({
  155. event,
  156. store,
  157. }: EventContext & StoreContext): Promise<void> {
  158. const [bucketId, sizeLimit, countLimit] = new Storage.StorageBucketVoucherLimitsSetEvent(event).params
  159. const bucket = await getById(store, StorageBucket, bucketId.toString())
  160. bucket.dataObjectsSizeLimit = sizeLimit
  161. bucket.dataObjectCountLimit = countLimit
  162. await store.save<StorageBucket>(bucket)
  163. }
  164. export async function storage_StorageBucketDeleted({ event, store }: EventContext & StoreContext): Promise<void> {
  165. const [bucketId] = new Storage.StorageBucketDeletedEvent(event).params
  166. // TODO: Cascade remove on db level (would require changes in Hydra / comitting autogenerated files)
  167. const storageBucket = await store.get(StorageBucket, {
  168. where: { id: bucketId.toString() },
  169. relations: ['bags', 'bags.storageBuckets'],
  170. })
  171. if (!storageBucket) {
  172. inconsistentState(`Storage bucket by id ${bucketId.toString()} not found!`)
  173. }
  174. // Remove relations
  175. await Promise.all(
  176. (storageBucket.bags || []).map((bag) => {
  177. bag.storageBuckets = (bag.storageBuckets || []).filter((bucket) => bucket.id !== bucketId.toString())
  178. return store.save<StorageBag>(bag)
  179. })
  180. )
  181. await store.remove<StorageBucket>(storageBucket)
  182. }
  183. // DYNAMIC BAGS
  184. export async function storage_DynamicBagCreated({ event, store }: EventContext & StoreContext): Promise<void> {
  185. const [bagId, , storageBucketIdsSet, distributionBucketIdsSet] = new Storage.DynamicBagCreatedEvent(event).params
  186. const storageBag = new StorageBag({
  187. id: getDynamicBagId(bagId),
  188. owner: getDynamicBagOwner(bagId),
  189. storageBuckets: Array.from(storageBucketIdsSet).map((id) => new StorageBucket({ id: id.toString() })),
  190. distributionBuckets: Array.from(distributionBucketIdsSet).map(
  191. (id) => new DistributionBucket({ id: distributionBucketId(id) })
  192. ),
  193. })
  194. await store.save<StorageBag>(storageBag)
  195. }
  196. export async function storage_DynamicBagDeleted({ event, store }: EventContext & StoreContext): Promise<void> {
  197. const [, bagId] = new Storage.DynamicBagDeletedEvent(event).params
  198. const storageBag = await getDynamicBag(store, bagId, ['objects'])
  199. await store.remove<StorageBag>(storageBag)
  200. }
  201. // DATA OBJECTS
  202. // Note: "Uploaded" here actually means "created" (the real upload happens later)
  203. export async function storage_DataObjectsUploaded({ event, store }: EventContext & StoreContext): Promise<void> {
  204. const [dataObjectIds, uploadParams, deletionPrize] = new Storage.DataObjectsUploadedEvent(event).params
  205. await createDataObjects(store, uploadParams, deletionPrize, dataObjectIds)
  206. }
  207. export async function storage_PendingDataObjectsAccepted({ event, store }: EventContext & StoreContext): Promise<void> {
  208. const [, , bagId, dataObjectIds] = new Storage.PendingDataObjectsAcceptedEvent(event).params
  209. const dataObjects = await getDataObjectsInBag(store, bagId, dataObjectIds)
  210. await Promise.all(
  211. dataObjects.map(async (dataObject) => {
  212. dataObject.isAccepted = true
  213. await store.save<StorageDataObject>(dataObject)
  214. })
  215. )
  216. }
  217. export async function storage_DataObjectsMoved({ event, store }: EventContext & StoreContext): Promise<void> {
  218. const [srcBagId, destBagId, dataObjectIds] = new Storage.DataObjectsMovedEvent(event).params
  219. const dataObjects = await getDataObjectsInBag(store, srcBagId, dataObjectIds)
  220. const destBag = await getBag(store, destBagId)
  221. await Promise.all(
  222. dataObjects.map(async (dataObject) => {
  223. dataObject.storageBag = destBag
  224. await store.save<StorageDataObject>(dataObject)
  225. })
  226. )
  227. }
  228. export async function storage_DataObjectsDeleted({ event, store }: EventContext & StoreContext): Promise<void> {
  229. const [, bagId, dataObjectIds] = new Storage.DataObjectsDeletedEvent(event).params
  230. const dataObjects = await getDataObjectsInBag(store, bagId, dataObjectIds)
  231. await Promise.all(dataObjects.map((o) => removeDataObject(store, o)))
  232. }
  233. // DISTRIBUTION FAMILY
  234. export async function storage_DistributionBucketFamilyCreated({
  235. event,
  236. store,
  237. }: EventContext & StoreContext): Promise<void> {
  238. const [familyId] = new Storage.DistributionBucketFamilyCreatedEvent(event).params
  239. const family = new DistributionBucketFamily({
  240. id: familyId.toString(),
  241. })
  242. await store.save<DistributionBucketFamily>(family)
  243. }
  244. export async function storage_DistributionBucketFamilyMetadataSet({
  245. event,
  246. store,
  247. }: EventContext & StoreContext): Promise<void> {
  248. const [familyId, metadataBytes] = new Storage.DistributionBucketFamilyMetadataSetEvent(event).params
  249. const family = await getDistributionBucketFamilyWithMetadata(store, familyId.toString())
  250. family.metadata = await processDistributionBucketFamilyMetadata(event, store, family.metadata, metadataBytes)
  251. await store.save<DistributionBucketFamily>(family)
  252. }
  253. export async function storage_DistributionBucketFamilyDeleted({
  254. event,
  255. store,
  256. }: EventContext & StoreContext): Promise<void> {
  257. const [familyId] = new Storage.DistributionBucketFamilyDeletedEvent(event).params
  258. const family = await getById(store, DistributionBucketFamily, familyId.toString())
  259. await store.remove(family)
  260. }
  261. // DISTRIBUTION BUCKET
  262. export async function storage_DistributionBucketCreated({ event, store }: EventContext & StoreContext): Promise<void> {
  263. const [familyId, acceptingNewBags, bucketId] = new Storage.DistributionBucketCreatedEvent(event).params
  264. const family = await getById(store, DistributionBucketFamily, familyId.toString())
  265. const bucket = new DistributionBucket({
  266. id: distributionBucketId(bucketId),
  267. bucketIndex: bucketId.distribution_bucket_index.toNumber(),
  268. acceptingNewBags: acceptingNewBags.valueOf(),
  269. distributing: true, // Runtime default
  270. family,
  271. })
  272. await store.save<DistributionBucket>(bucket)
  273. }
  274. export async function storage_DistributionBucketStatusUpdated({
  275. event,
  276. store,
  277. }: EventContext & StoreContext): Promise<void> {
  278. const [bucketId, acceptingNewBags] = new Storage.DistributionBucketStatusUpdatedEvent(event).params
  279. const bucket = await getById(store, DistributionBucket, distributionBucketId(bucketId))
  280. bucket.acceptingNewBags = acceptingNewBags.valueOf()
  281. await store.save<DistributionBucket>(bucket)
  282. }
  283. export async function storage_DistributionBucketDeleted({ event, store }: EventContext & StoreContext): Promise<void> {
  284. const [bucketId] = new Storage.DistributionBucketDeletedEvent(event).params
  285. // TODO: Cascade remove on db level (would require changes in Hydra / comitting autogenerated files)
  286. const distributionBucket = await store.get(DistributionBucket, {
  287. where: { id: distributionBucketId(bucketId) },
  288. relations: ['bags', 'bags.distributionBuckets'],
  289. })
  290. if (!distributionBucket) {
  291. inconsistentState(`Distribution bucket by id ${distributionBucketId(bucketId)} not found!`)
  292. }
  293. // Remove relations
  294. await Promise.all(
  295. (distributionBucket.bags || []).map((bag) => {
  296. bag.distributionBuckets = (bag.distributionBuckets || []).filter(
  297. (bucket) => bucket.id !== distributionBucketId(bucketId)
  298. )
  299. return store.save<StorageBag>(bag)
  300. })
  301. )
  302. await store.remove<DistributionBucket>(distributionBucket)
  303. }
  304. export async function storage_DistributionBucketsUpdatedForBag({
  305. event,
  306. store,
  307. }: EventContext & StoreContext): Promise<void> {
  308. const [
  309. bagId,
  310. familyId,
  311. addedBucketsIndices,
  312. removedBucketsIndices,
  313. ] = new Storage.DistributionBucketsUpdatedForBagEvent(event).params
  314. // Get or create bag
  315. const storageBag = await getBag(store, bagId, ['distributionBuckets'])
  316. const removedBucketsIds = Array.from(removedBucketsIndices).map((bucketIndex) =>
  317. distributionBucketIdByFamilyAndIndex(familyId, bucketIndex)
  318. )
  319. const addedBucketsIds = Array.from(addedBucketsIndices).map((bucketIndex) =>
  320. distributionBucketIdByFamilyAndIndex(familyId, bucketIndex)
  321. )
  322. storageBag.distributionBuckets = (storageBag.distributionBuckets || [])
  323. .filter((bucket) => !removedBucketsIds.includes(bucket.id))
  324. .concat(addedBucketsIds.map((id) => new DistributionBucket({ id })))
  325. await store.save<StorageBag>(storageBag)
  326. }
  327. export async function storage_DistributionBucketModeUpdated({
  328. event,
  329. store,
  330. }: EventContext & StoreContext): Promise<void> {
  331. const [bucketId, distributing] = new Storage.DistributionBucketModeUpdatedEvent(event).params
  332. const bucket = await getById(store, DistributionBucket, distributionBucketId(bucketId))
  333. bucket.distributing = distributing.valueOf()
  334. await store.save<DistributionBucket>(bucket)
  335. }
  336. export async function storage_DistributionBucketOperatorInvited({
  337. event,
  338. store,
  339. }: EventContext & StoreContext): Promise<void> {
  340. const [bucketId, workerId] = new Storage.DistributionBucketOperatorInvitedEvent(event).params
  341. const bucket = await getById(store, DistributionBucket, distributionBucketId(bucketId))
  342. const invitedOperator = new DistributionBucketOperator({
  343. id: distributionOperatorId(bucketId, workerId),
  344. distributionBucket: bucket,
  345. status: DistributionBucketOperatorStatus.INVITED,
  346. workerId: workerId.toNumber(),
  347. })
  348. await store.save<DistributionBucketOperator>(invitedOperator)
  349. }
  350. export async function storage_DistributionBucketInvitationCancelled({
  351. event,
  352. store,
  353. }: EventContext & StoreContext): Promise<void> {
  354. const [bucketId, workerId] = new Storage.DistributionBucketOperatorInvitedEvent(event).params
  355. const invitedOperator = await getById(store, DistributionBucketOperator, distributionOperatorId(bucketId, workerId))
  356. await store.remove<DistributionBucketOperator>(invitedOperator)
  357. }
  358. export async function storage_DistributionBucketInvitationAccepted({
  359. event,
  360. store,
  361. }: EventContext & StoreContext): Promise<void> {
  362. const [workerId, bucketId] = new Storage.DistributionBucketInvitationAcceptedEvent(event).params
  363. const invitedOperator = await getById(store, DistributionBucketOperator, distributionOperatorId(bucketId, workerId))
  364. invitedOperator.status = DistributionBucketOperatorStatus.ACTIVE
  365. await store.save<DistributionBucketOperator>(invitedOperator)
  366. }
  367. export async function storage_DistributionBucketMetadataSet({
  368. event,
  369. store,
  370. }: EventContext & StoreContext): Promise<void> {
  371. const [workerId, bucketId, metadataBytes] = new Storage.DistributionBucketMetadataSetEvent(event).params
  372. const operator = await getDistributionBucketOperatorWithMetadata(store, distributionOperatorId(bucketId, workerId))
  373. operator.metadata = await processDistributionOperatorMetadata(event, store, operator.metadata, metadataBytes)
  374. await store.save<DistributionBucketOperator>(operator)
  375. }
  376. export async function storage_DistributionBucketOperatorRemoved({
  377. event,
  378. store,
  379. }: EventContext & StoreContext): Promise<void> {
  380. const [bucketId, workerId] = new Storage.DistributionBucketOperatorRemovedEvent(event).params
  381. // TODO: Cascade remove on db level (would require changes in Hydra / comitting autogenerated files)
  382. const operator = await getDistributionBucketOperatorWithMetadata(store, distributionOperatorId(bucketId, workerId))
  383. await store.remove<DistributionBucketOperator>(operator)
  384. if (operator.metadata) {
  385. await store.remove<DistributionBucketOperatorMetadata>(operator.metadata)
  386. if (operator.metadata.nodeLocation) {
  387. await store.remove<NodeLocationMetadata>(operator.metadata.nodeLocation)
  388. if (operator.metadata.nodeLocation.coordinates) {
  389. await store.remove<GeoCoordinates>(operator.metadata.nodeLocation.coordinates)
  390. }
  391. }
  392. }
  393. }
  394. // STORAGE SYSTEM GLOBAL PARAMS
  395. export async function storage_UpdateBlacklist({ event, store }: EventContext & StoreContext): Promise<void> {
  396. const [removedContentIds, addedContentIds] = new Storage.UpdateBlacklistEvent(event).params
  397. const storageSystem = await getStorageSystem(store)
  398. storageSystem.blacklist = storageSystem.blacklist
  399. .filter((cid) => !Array.from(removedContentIds).some((id) => id.eq(cid)))
  400. .concat(Array.from(addedContentIds).map((id) => id.toString()))
  401. await store.save<StorageSystemParameters>(storageSystem)
  402. }
  403. export async function storage_DistributionBucketsPerBagLimitUpdated({
  404. event,
  405. store,
  406. }: EventContext & StoreContext): Promise<void> {
  407. const [newLimit] = new Storage.DistributionBucketsPerBagLimitUpdatedEvent(event).params
  408. const storageSystem = await getStorageSystem(store)
  409. storageSystem.distributionBucketsPerBagLimit = newLimit.toNumber()
  410. await store.save<StorageSystemParameters>(storageSystem)
  411. }
  412. export async function storage_StorageBucketsPerBagLimitUpdated({
  413. event,
  414. store,
  415. }: EventContext & StoreContext): Promise<void> {
  416. const [newLimit] = new Storage.StorageBucketsPerBagLimitUpdatedEvent(event).params
  417. const storageSystem = await getStorageSystem(store)
  418. storageSystem.storageBucketsPerBagLimit = newLimit.toNumber()
  419. await store.save<StorageSystemParameters>(storageSystem)
  420. }
  421. export async function storage_StorageBucketsVoucherMaxLimitsUpdated({
  422. event,
  423. store,
  424. }: EventContext & StoreContext): Promise<void> {
  425. const [sizeLimit, countLimit] = new Storage.StorageBucketsVoucherMaxLimitsUpdatedEvent(event).params
  426. const storageSystem = await getStorageSystem(store)
  427. storageSystem.storageBucketMaxObjectsSizeLimit = sizeLimit
  428. storageSystem.storageBucketMaxObjectsCountLimit = countLimit
  429. await store.save<StorageSystemParameters>(storageSystem)
  430. }
  431. export async function storage_UploadingBlockStatusUpdated({
  432. event,
  433. store,
  434. }: EventContext & StoreContext): Promise<void> {
  435. const [isBlocked] = new Storage.UploadingBlockStatusUpdatedEvent(event).params
  436. const storageSystem = await getStorageSystem(store)
  437. storageSystem.uploadingBlocked = isBlocked.isTrue
  438. await store.save<StorageSystemParameters>(storageSystem)
  439. }
  440. export async function storage_DataObjectPerMegabyteFeeUpdated({
  441. event,
  442. store,
  443. }: EventContext & StoreContext): Promise<void> {
  444. const [newFee] = new Storage.DataObjectPerMegabyteFeeUpdatedEvent(event).params
  445. const storageSystem = await getStorageSystem(store)
  446. storageSystem.dataObjectFeePerMb = newFee
  447. await store.save<StorageSystemParameters>(storageSystem)
  448. }