EntityBatchParser.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import { AddClassSchema } from '../../types/extrinsics/AddClassSchema'
  2. import { FetchedInput } from './inputs'
  3. import { createType } from '@joystream/types'
  4. import { blake2AsHex } from '@polkadot/util-crypto'
  5. import { OperationType, ParametrizedPropertyValue } from '@joystream/types/content-directory'
  6. // TODO: Make it more typesafe
  7. type Batch = Record<string, any>[]
  8. type FetchedBatch = FetchedInput<Batch>
  9. export class EntityBatchesParser {
  10. schemaInputs: FetchedInput<AddClassSchema>[]
  11. batchInputs: FetchedBatch[]
  12. operations: OperationType[] = []
  13. entityIndexByUniqueQueryMap = new Map<string, number>()
  14. constructor(schemaInputs: FetchedInput<AddClassSchema>[], batchInputs: FetchedBatch[]) {
  15. this.schemaInputs = schemaInputs
  16. this.batchInputs = batchInputs
  17. }
  18. private schemaByEntityBatchFilename(entityBatchFilename: string) {
  19. const foundSchema = this.schemaInputs.find(
  20. ({ fileName: schemaFilename }) => schemaFilename.replace('Schema.json', 'Batch.json') === entityBatchFilename
  21. )
  22. if (!foundSchema) {
  23. throw new Error(`Related schema not found for entity batch: ${entityBatchFilename}`)
  24. }
  25. return foundSchema.data
  26. }
  27. private schemaByClassId(classId: number) {
  28. const foundSchema = this.schemaInputs.find(({ fileName }) => parseInt(fileName.split('_')[0]) === classId)
  29. if (!foundSchema) {
  30. throw new Error(`Schema not found by class id: ${classId}`)
  31. }
  32. return foundSchema.data
  33. }
  34. private getRefEntitySchema(parentEntitySchema: AddClassSchema, propName: string) {
  35. const refProp = parentEntitySchema.newProperties.find((p) => p.name === propName)
  36. if (!refProp) {
  37. throw new Error(`findRefEntitySchema: Property ${propName} not found in class ${parentEntitySchema.classId}`)
  38. }
  39. const refClassId = parseInt(
  40. createType('PropertyType', refProp.property_type).asType('Single').asType('Reference')[0].toString()
  41. )
  42. return this.schemaByClassId(refClassId)
  43. }
  44. private getUniqueQueryHash(uniquePropVal: Record<string, any>, classId: number) {
  45. return blake2AsHex(JSON.stringify([classId, uniquePropVal]))
  46. }
  47. private findEntityIndexByUniqueQuery(uniquePropVal: Record<string, any>, classId: number) {
  48. const hash = this.getUniqueQueryHash(uniquePropVal, classId)
  49. const foundIndex = this.entityIndexByUniqueQueryMap.get(hash)
  50. if (foundIndex === undefined) {
  51. throw new Error(
  52. `findEntityIndexByUniqueQuery failed for class ${classId} and query: ${JSON.stringify(uniquePropVal)}`
  53. )
  54. }
  55. return foundIndex
  56. }
  57. private parseEntityInput(entityInput: Record<string, any>, schema: AddClassSchema) {
  58. const parametrizedPropertyValues = Object.entries(entityInput).map(([propertyName, propertyValue]) => {
  59. const schemaPropertyIndex = schema.newProperties.findIndex((p) => p.name === propertyName)
  60. const schemaPropertyType = createType('PropertyType', schema.newProperties[schemaPropertyIndex].property_type)
  61. let value: ParametrizedPropertyValue
  62. // Handle references
  63. if (schemaPropertyType.isOfType('Single') && schemaPropertyType.asType('Single').isOfType('Reference')) {
  64. const refEntitySchema = this.getRefEntitySchema(schema, propertyName)
  65. let entityIndex: number
  66. if (Object.keys(propertyValue).includes('new')) {
  67. entityIndex = this.parseEntityInput(propertyValue.new, refEntitySchema)
  68. } else if (Object.keys(propertyValue).includes('existing')) {
  69. entityIndex = this.findEntityIndexByUniqueQuery(propertyValue.existing, refEntitySchema.classId)
  70. } else {
  71. throw new Error(`Invalid reference property value: ${JSON.stringify(propertyValue)}`)
  72. }
  73. value = createType('ParametrizedPropertyValue', { InternalEntityJustAdded: entityIndex })
  74. } else {
  75. value = createType('ParametrizedPropertyValue', {
  76. InputPropertyValue: schemaPropertyType.toInputPropertyValue(propertyValue).toJSON(),
  77. })
  78. }
  79. return {
  80. in_class_index: schemaPropertyIndex,
  81. value: value.toJSON(),
  82. }
  83. })
  84. // Add operations
  85. const createEntityOperationIndex = this.operations.length
  86. this.operations.push(createType('OperationType', { CreateEntity: { class_id: schema.classId } }))
  87. this.operations.push(
  88. createType('OperationType', {
  89. AddSchemaSupportToEntity: {
  90. schema_id: 0,
  91. entity_id: { InternalEntityJustAdded: createEntityOperationIndex },
  92. parametrized_property_values: parametrizedPropertyValues,
  93. },
  94. })
  95. )
  96. // Add entries to entityIndexByUniqueQueryMap
  97. schema.newProperties
  98. .filter((p) => p.unique)
  99. .forEach(({ name }) => {
  100. const hash = this.getUniqueQueryHash({ [name]: entityInput[name] }, schema.classId)
  101. this.entityIndexByUniqueQueryMap.set(hash, createEntityOperationIndex)
  102. })
  103. // Return CreateEntity operation index
  104. return createEntityOperationIndex
  105. }
  106. private reset() {
  107. this.entityIndexByUniqueQueryMap = new Map<string, number>()
  108. this.operations = []
  109. }
  110. public getOperations() {
  111. this.batchInputs.forEach(({ fileName: batchFilename, data: batch }) => {
  112. const entitySchema = this.schemaByEntityBatchFilename(batchFilename)
  113. batch.forEach((entityInput) => this.parseEntityInput(entityInput, entitySchema))
  114. })
  115. const operations = this.operations
  116. this.reset()
  117. return operations
  118. }
  119. }