|
@@ -1,7 +1,7 @@
|
|
|
import { AddClassSchema, Property } from '../../types/extrinsics/AddClassSchema'
|
|
|
import { createType } from '@joystream/types'
|
|
|
-import { blake2AsHex } from '@polkadot/util-crypto'
|
|
|
import {
|
|
|
+ InputEntityValuesMap,
|
|
|
ClassId,
|
|
|
OperationType,
|
|
|
ParametrizedPropertyValue,
|
|
@@ -10,14 +10,19 @@ import {
|
|
|
EntityId,
|
|
|
Entity,
|
|
|
ParametrizedClassPropertyValue,
|
|
|
+ InputPropertyValue,
|
|
|
} from '@joystream/types/content-directory'
|
|
|
+import { blake2AsHex } from '@polkadot/util-crypto'
|
|
|
import { isSingle, isReference } from './propertyType'
|
|
|
import { ApiPromise } from '@polkadot/api'
|
|
|
import { JoyBTreeSet } from '@joystream/types/common'
|
|
|
import { CreateClass } from '../../types/extrinsics/CreateClass'
|
|
|
import { EntityBatch } from '../../types/EntityBatch'
|
|
|
import { getInputs } from './inputs'
|
|
|
-import { SubmittableExtrinsic } from '@polkadot/api/types'
|
|
|
+
|
|
|
+type SimpleEntityValue = string | boolean | number | string[] | boolean[] | number[] | undefined
|
|
|
+// Input without "new" or "extising" keywords
|
|
|
+type SimpleEntityInput = { [K: string]: SimpleEntityValue }
|
|
|
|
|
|
export class InputParser {
|
|
|
private api: ApiPromise
|
|
@@ -31,8 +36,6 @@ export class InputParser {
|
|
|
private entityIdByUniqueQueryMap = new Map<string, number>()
|
|
|
private entityByUniqueQueryCurrentIndex = 0
|
|
|
private classIdByNameMap = new Map<string, number>()
|
|
|
- private classMapInitialized = false
|
|
|
- private entityIdByUniqueQueryMapInitialized = false
|
|
|
|
|
|
static createWithInitialInputs(api: ApiPromise): InputParser {
|
|
|
return new InputParser(
|
|
@@ -64,30 +67,28 @@ export class InputParser {
|
|
|
this.batchInputs = batchInputs || []
|
|
|
}
|
|
|
|
|
|
- private async initializeClassMap() {
|
|
|
- if (this.classMapInitialized) {
|
|
|
- return
|
|
|
- }
|
|
|
+ private async loadClassMap() {
|
|
|
+ this.classIdByNameMap = new Map<string, number>()
|
|
|
+
|
|
|
const classEntries = await this.api.query.contentDirectory.classById.entries()
|
|
|
classEntries.forEach(([key, aClass]) => {
|
|
|
this.classIdByNameMap.set(aClass.name.toString(), (key.args[0] as ClassId).toNumber())
|
|
|
})
|
|
|
- this.classMapInitialized = true
|
|
|
}
|
|
|
|
|
|
- // Initialize entityIdByUniqueQueryMap with entities fetched from the chain
|
|
|
- private async initializeEntityIdByUniqueQueryMap() {
|
|
|
- if (this.entityIdByUniqueQueryMapInitialized) {
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- await this.initializeClassMap() // Initialize if not yet initialized
|
|
|
+ private async loadEntityIdByUniqueQueryMap() {
|
|
|
+ this.entityIdByUniqueQueryMap = new Map<string, number>()
|
|
|
|
|
|
// Get entity entries
|
|
|
const entityEntries: [EntityId, Entity][] = (
|
|
|
await this.api.query.contentDirectory.entityById.entries()
|
|
|
).map(([storageKey, entity]) => [storageKey.args[0] as EntityId, entity])
|
|
|
|
|
|
+ // Since we use classMap directly we need to make sure it's loaded first
|
|
|
+ if (!this.classIdByNameMap.size) {
|
|
|
+ await this.loadClassMap()
|
|
|
+ }
|
|
|
+
|
|
|
entityEntries.forEach(([entityId, entity]) => {
|
|
|
const classId = entity.class_id.toNumber()
|
|
|
const className = Array.from(this.classIdByNameMap.entries()).find(([, id]) => id === classId)?.[0]
|
|
@@ -121,8 +122,6 @@ export class InputParser {
|
|
|
this.entityIdByUniqueQueryMap.set(hash, entityId.toNumber())
|
|
|
})
|
|
|
})
|
|
|
-
|
|
|
- this.entityIdByUniqueQueryMapInitialized = true
|
|
|
}
|
|
|
|
|
|
private schemaByClassName(className: string) {
|
|
@@ -152,30 +151,40 @@ export class InputParser {
|
|
|
|
|
|
// Seatch for entity by { [uniquePropName]: [uniquePropVal] } on chain
|
|
|
async findEntityIdByUniqueQuery(uniquePropVal: Record<string, any>, className: string): Promise<number> {
|
|
|
- await this.initializeEntityIdByUniqueQueryMap()
|
|
|
const hash = this.getUniqueQueryHash(uniquePropVal, className)
|
|
|
- const foundId = this.entityIdByUniqueQueryMap.get(hash)
|
|
|
+ let foundId = this.entityIdByUniqueQueryMap.get(hash)
|
|
|
if (foundId === undefined) {
|
|
|
- throw new Error(
|
|
|
- `findEntityIdByUniqueQuery failed for class ${className} and query: ${JSON.stringify(uniquePropVal)}`
|
|
|
- )
|
|
|
+ // Try to re-load the map and find again
|
|
|
+ await this.loadEntityIdByUniqueQueryMap()
|
|
|
+ foundId = this.entityIdByUniqueQueryMap.get(hash)
|
|
|
+ if (foundId === undefined) {
|
|
|
+ // If still not found - throw
|
|
|
+ throw new Error(
|
|
|
+ `findEntityIdByUniqueQuery failed for class ${className} and query: ${JSON.stringify(uniquePropVal)}`
|
|
|
+ )
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
return foundId
|
|
|
}
|
|
|
|
|
|
- private getClassIdByName(className: string): number {
|
|
|
- const classId = this.classIdByNameMap.get(className)
|
|
|
+ async getClassIdByName(className: string): Promise<number> {
|
|
|
+ let classId = this.classIdByNameMap.get(className)
|
|
|
if (classId === undefined) {
|
|
|
- throw new Error(`Could not find class id by name: "${className}"!`)
|
|
|
+ // Try to re-load the map
|
|
|
+ await this.loadClassMap()
|
|
|
+ classId = this.classIdByNameMap.get(className)
|
|
|
+ if (classId === undefined) {
|
|
|
+ // If still not found - throw
|
|
|
+ throw new Error(`Could not find class id by name: "${className}"!`)
|
|
|
+ }
|
|
|
}
|
|
|
return classId
|
|
|
}
|
|
|
|
|
|
- private parsePropertyType(propertyType: Property['property_type']): PropertyType {
|
|
|
+ private async parsePropertyType(propertyType: Property['property_type']): Promise<PropertyType> {
|
|
|
if (isSingle(propertyType) && isReference(propertyType.Single)) {
|
|
|
const { className, sameOwner } = propertyType.Single.Reference
|
|
|
- const classId = this.getClassIdByName(className)
|
|
|
+ const classId = await this.getClassIdByName(className)
|
|
|
return createType('PropertyType', { Single: { Reference: [classId, sameOwner] } })
|
|
|
}
|
|
|
// Types other than reference are fully compatible
|
|
@@ -224,7 +233,9 @@ export class InputParser {
|
|
|
let value = customHandler && (await customHandler(schemaProperty, propertyValue))
|
|
|
if (value === undefined) {
|
|
|
value = createType('ParametrizedPropertyValue', {
|
|
|
- InputPropertyValue: this.parsePropertyType(schemaProperty.property_type).toInputPropertyValue(propertyValue),
|
|
|
+ InputPropertyValue: (await this.parsePropertyType(schemaProperty.property_type)).toInputPropertyValue(
|
|
|
+ propertyValue
|
|
|
+ ),
|
|
|
})
|
|
|
}
|
|
|
|
|
@@ -299,7 +310,7 @@ export class InputParser {
|
|
|
} else {
|
|
|
// Add operations (createEntity, AddSchemaSupportToEntity)
|
|
|
const createEntityOperationIndex = this.createEntityOperations.length
|
|
|
- const classId = this.getClassIdByName(schema.className)
|
|
|
+ const classId = await this.getClassIdByName(schema.className)
|
|
|
this.createEntityOperations.push(createType('OperationType', { CreateEntity: { class_id: classId } }))
|
|
|
this.addSchemaToEntityOprations.push(
|
|
|
createType('OperationType', {
|
|
@@ -318,10 +329,7 @@ export class InputParser {
|
|
|
|
|
|
private reset() {
|
|
|
this.entityIndexByUniqueQueryMap = new Map<string, number>()
|
|
|
- this.entityIdByUniqueQueryMapInitialized = false
|
|
|
-
|
|
|
this.classIdByNameMap = new Map<string, number>()
|
|
|
- this.classMapInitialized = false
|
|
|
|
|
|
this.createEntityOperations = []
|
|
|
this.addSchemaToEntityOprations = []
|
|
@@ -331,7 +339,6 @@ export class InputParser {
|
|
|
}
|
|
|
|
|
|
public async getEntityBatchOperations() {
|
|
|
- await this.initializeClassMap()
|
|
|
// First - create entityUniqueQueryMap to allow referencing any entity at any point
|
|
|
this.batchInputs.forEach((batch) => {
|
|
|
const entitySchema = this.schemaByClassName(batch.className)
|
|
@@ -356,7 +363,6 @@ export class InputParser {
|
|
|
className: string,
|
|
|
entityId: number
|
|
|
): Promise<OperationType[]> {
|
|
|
- await this.initializeClassMap()
|
|
|
const schema = this.schemaByClassName(className)
|
|
|
await this.parseEntityInput(input, schema, entityId)
|
|
|
const operations = [
|
|
@@ -370,13 +376,14 @@ export class InputParser {
|
|
|
}
|
|
|
|
|
|
public async parseAddClassSchemaExtrinsic(inputData: AddClassSchema) {
|
|
|
- await this.initializeClassMap() // Initialize if not yet initialized
|
|
|
- const classId = this.getClassIdByName(inputData.className)
|
|
|
- const newProperties = inputData.newProperties.map((p) => ({
|
|
|
- ...p,
|
|
|
- // Parse different format for Reference (and potentially other propTypes in the future)
|
|
|
- property_type: this.parsePropertyType(p.property_type).toJSON(),
|
|
|
- }))
|
|
|
+ const classId = await this.getClassIdByName(inputData.className)
|
|
|
+ const newProperties = await Promise.all(
|
|
|
+ inputData.newProperties.map(async (p) => ({
|
|
|
+ ...p,
|
|
|
+ // Parse different format for Reference (and potentially other propTypes in the future)
|
|
|
+ property_type: (await this.parsePropertyType(p.property_type)).toJSON(),
|
|
|
+ }))
|
|
|
+ )
|
|
|
return this.api.tx.contentDirectory.addClassSchema(
|
|
|
classId,
|
|
|
new (JoyBTreeSet(PropertyId))(this.api.registry, inputData.existingProperties),
|
|
@@ -401,4 +408,27 @@ export class InputParser {
|
|
|
public getCreateClassExntrinsics() {
|
|
|
return this.classInputs.map((data) => this.parseCreateClassExtrinsic(data))
|
|
|
}
|
|
|
+
|
|
|
+ // Helper parser for "standalone" extrinsics like addSchemaSupportToEntity / updateEntityPropertyValues
|
|
|
+ public async parseToInputEntityValuesMap(
|
|
|
+ inputData: SimpleEntityInput,
|
|
|
+ className: string
|
|
|
+ ): Promise<InputEntityValuesMap> {
|
|
|
+ await this.parseEntityInput(inputData, this.schemaByClassName(className))
|
|
|
+ const inputPropValMap = new Map<PropertyId, InputPropertyValue>()
|
|
|
+
|
|
|
+ const [operation] = this.addSchemaToEntityOprations
|
|
|
+ operation
|
|
|
+ .asType('AddSchemaSupportToEntity')
|
|
|
+ .parametrized_property_values /* First we need to sort by propertyId, since otherwise there will be issues
|
|
|
+ when encoding the BTreeMap (similar to BTreeSet) */
|
|
|
+ .sort((a, b) => a.in_class_index.toNumber() - b.in_class_index.toNumber())
|
|
|
+ .map((pcpv) => {
|
|
|
+ inputPropValMap.set(pcpv.in_class_index, pcpv.value.asType('InputPropertyValue'))
|
|
|
+ })
|
|
|
+
|
|
|
+ this.reset()
|
|
|
+
|
|
|
+ return createType('InputEntityValuesMap', inputPropValMap)
|
|
|
+ }
|
|
|
}
|