Browse Source

Merge pull request #1532 from Lezek123/createtype-ts-support

createType - TypeScript support
Mokhtar Naamani 4 years ago
parent
commit
5044d0b6e0

+ 4 - 7
content-directory-schemas/src/helpers/InputParser.ts

@@ -143,16 +143,13 @@ export class InputParser {
         let value = customHandler && customHandler(schemaProperty, propertyValue)
         if (value === undefined) {
           value = createType('ParametrizedPropertyValue', {
-            InputPropertyValue: this.parsePropertyType(schemaProperty.property_type)
-              .toInputPropertyValue(propertyValue)
-              .toJSON(),
+            InputPropertyValue: this.parsePropertyType(schemaProperty.property_type).toInputPropertyValue(
+              propertyValue
+            ),
           })
         }
 
-        return {
-          in_class_index: schemaPropertyIndex,
-          value: value.toJSON(),
-        }
+        return { in_class_index: schemaPropertyIndex, value }
       })
   }
 

+ 4 - 4
pioneer/packages/joy-forum/src/Context.tsx

@@ -232,10 +232,10 @@ function reducer (state: ForumState, action: ForumAction): ForumState {
         moderator_id: createType('AccountId', moderator),
         rationale: createType('Text', rationale)
       });
-      const threadUpd = createType('Thread', Object.assign(
-        thread.cloneValues(),
-        { moderation: createType('Option<ModerationAction>', moderation) }
-      ));
+      const threadUpd = createType('Thread', {
+        ...thread.cloneValues(),
+        moderation: createType('Option<ModerationAction>', moderation)
+      });
 
       threadById.set(id, threadUpd);
 

+ 2 - 2
pioneer/packages/joy-forum/src/calls.tsx

@@ -17,12 +17,12 @@ const storage: StorageType = 'substrate';
 type EntityMapName = 'categoryById' | 'threadById' | 'replyById';
 
 const getReactValue = (state: ForumState, endpoint: string, paramValue: any) => {
-  function getEntityById<T extends keyof InterfaceTypes>
+  function getEntityById<T extends 'Category' | 'Thread' | 'Reply'>
   (mapName: EntityMapName, type: T): InterfaceTypes[T] {
     const id = (paramValue as u64).toNumber();
     const entity = state[mapName].get(id);
 
-    return createType(type, entity);
+    return createType(type, entity as any);
   }
 
   switch (endpoint) {

+ 2 - 2
pioneer/packages/joy-proposals/src/stories/data/ProposalDetails.mock.ts

@@ -20,8 +20,8 @@ const mockedProposal: ParsedProposal = {
   proposerId: 303,
   status: createType('ProposalStatus', {
     Active: {
-      stakeId: 0,
-      sourceAccountId: '5C4hrfkRjSLwQSFVtCvtbV6wctV1WFnkiexUZWLAh4Bc7jib'
+      stake_id: 0,
+      source_account_id: '5C4hrfkRjSLwQSFVtCvtbV6wctV1WFnkiexUZWLAh4Bc7jib'
     }
   }),
   proposer: {

+ 2 - 2
pioneer/packages/joy-roles/src/classifiers.spec.ts

@@ -75,7 +75,7 @@ describe('hiring.Opening-> OpeningStageClassification', (): void => {
           stage: createType('OpeningStage', {
             Active: {
               stage: createType('ActiveOpeningStage', {
-                acceptingApplications: {
+                AcceptingApplications: {
                   started_accepting_applicants_at_block: 100
                 }
               })
@@ -101,7 +101,7 @@ describe('hiring.Opening-> OpeningStageClassification', (): void => {
           stage: createType('OpeningStage', {
             Active: {
               stage: createType('ActiveOpeningStage', {
-                reviewPeriod: {
+                ReviewPeriod: {
                   started_accepting_applicants_at_block: 100,
                   started_review_period_at_block: 100
                 }

+ 4 - 2
pioneer/packages/joy-roles/src/tabs/Admin.controller.tsx

@@ -601,8 +601,10 @@ const NewOpening = (props: NewOpeningProps) => {
   };
 
   const onChangeExactBlock = (e: any, { value }: InputOnChangeData) => {
-    setExactBlock(typeof value === 'number' ? value : (parseInt(value) || 0));
-    setStart(createType('ActivateOpeningAt', { ExactBlock: value }));
+    const valueInt = typeof value === 'number' ? value : (parseInt(value) || 0);
+
+    setExactBlock(valueInt);
+    setStart(createType('ActivateOpeningAt', { ExactBlock: valueInt }));
   };
 
   const [policy, setPolicy] = useState(props.desc.policy);

+ 1 - 1
types/src/JoyEnum.ts

@@ -9,7 +9,7 @@ export interface ExtendedEnum<Types extends Record<string, Constructor>> extends
   type: keyof Types & string // More typesafe type for the original Enum property
 }
 
-export interface ExtendedEnumConstructor<Types extends Record<string, Constructor>>
+export interface ExtendedEnumConstructor<Types extends Record<string, Constructor> = Record<string, Constructor>>
   extends EnumConstructor<ExtendedEnum<Types>> {
   create<TypeKey extends keyof Types>(
     registry: Registry,

+ 12 - 5
types/src/JoyStruct.ts

@@ -4,7 +4,8 @@ import { Codec, Constructor, Registry } from '@polkadot/types/types'
 export interface ExtendedStruct<FieldTypes extends Record<string, Constructor>> extends Struct<FieldTypes> {
   getField<FieldKey extends keyof FieldTypes>(key: FieldKey): InstanceType<FieldTypes[FieldKey]>
   getString<FieldKey extends keyof FieldTypes>(key: FieldKey): string
-  cloneValues(): { [k in keyof FieldTypes]: FieldTypes[k] }
+  cloneValues(): { [k in keyof FieldTypes]: InstanceType<FieldTypes[k]> }
+  typeDefs: FieldTypes
 }
 
 // Those getters are automatically added via Object.defineProperty when using Struct.with
@@ -27,12 +28,16 @@ export interface StructConstructor<
 export type ExtendedStructConstructor<FieldTypes extends Record<string, Constructor>> = StructConstructor<
   FieldTypes,
   ExtendedStruct<FieldTypes>
->
+> & {
+  typeDefs: FieldTypes
+}
 
 export type ExtendedStructDecoratedConstructor<FieldTypes extends Record<string, Constructor>> = StructConstructor<
   FieldTypes,
   ExtendedStructDecorated<FieldTypes>
->
+> & {
+  typeDefs: FieldTypes
+}
 
 // Helper for creating extended Struct type with TS-compatible interface
 // It's called JoyStructCustom, because eventually we'd want to migrate all structs to JoyStructDecorated,
@@ -42,6 +47,8 @@ export function JoyStructCustom<FieldTypes extends Record<string, Constructor>>(
   fields: FieldTypes
 ): ExtendedStructConstructor<FieldTypes> {
   return class JoyStructObject extends Struct.with(fields) {
+    static typeDefs = fields
+    typeDefs = JoyStructObject.typeDefs
     // eslint-disable-next-line no-useless-constructor
     constructor(registry: Registry, value?: { [k in keyof FieldTypes]: InstanceType<FieldTypes[k]> }) {
       super(registry, value)
@@ -56,14 +63,14 @@ export function JoyStructCustom<FieldTypes extends Record<string, Constructor>>(
     }
 
     // TODO: Check why would this ever be needed
-    cloneValues(): { [k in keyof FieldTypes]: FieldTypes[k] } {
+    cloneValues(): { [k in keyof FieldTypes]: InstanceType<FieldTypes[k]> } {
       const objectClone = {} as Partial<{ [k in keyof FieldTypes]: Codec }>
 
       super.forEach((v, k) => {
         objectClone[k] = v // shallow copy acceptable ?
       })
 
-      return (objectClone as unknown) as { [k in keyof FieldTypes]: FieldTypes[k] }
+      return objectClone as { [k in keyof FieldTypes]: InstanceType<FieldTypes[k]> }
     }
   }
 }

+ 41 - 3
types/src/index.ts

@@ -1,4 +1,4 @@
-import { RegistryTypes } from '@polkadot/types/types'
+import { Codec, RegistryTypes } from '@polkadot/types/types'
 import common from './common'
 import members from './members'
 import council from './council'
@@ -15,7 +15,10 @@ import media from './media'
 import proposals from './proposals'
 import contentDirectory from './content-directory'
 import { InterfaceTypes } from '@polkadot/types/types/registry'
-import { TypeRegistry } from '@polkadot/types'
+import { TypeRegistry, Text, UInt, Null, bool, Option, Vec, BTreeSet } from '@polkadot/types'
+import { ExtendedEnum } from './JoyEnum'
+import { ExtendedStruct } from './JoyStruct'
+import BN from 'bn.js'
 
 export {
   common,
@@ -57,9 +60,44 @@ export const types: RegistryTypes = {
 export const registry = new TypeRegistry()
 registry.register(types)
 
+// Tweaked version of https://stackoverflow.com/a/62163715 for handling enum variants
+// Based on type (T) like: { a: string; b: number; c: Null; }
+// will create a type like: { a: string } | { b: number } | { c: Null } | "c"
+type EnumVariant<T> = keyof T extends infer K
+  ? K extends keyof T
+    ? T[K] extends Null
+      ? K
+      : { [I in K]: T[I] }
+    : never
+  : never
+
+// Create simple interface for any Codec type (inlcuding JoyEnums and JoyStructs)
+// Cannot handle Option here, since that would cause circular reference error
+type CreateInterface_NoOption<T extends Codec> =
+  | T
+  | (T extends ExtendedEnum<infer S>
+      ? EnumVariant<{ [K in keyof S]: CreateInterface<InstanceType<T['typeDefinitions'][K]>> }>
+      : T extends ExtendedStruct<infer S>
+      ? { [K in keyof S]?: CreateInterface<InstanceType<T['typeDefs'][K]>> }
+      : T extends Text
+      ? string
+      : T extends UInt
+      ? number | BN
+      : T extends bool
+      ? boolean
+      : T extends Vec<infer S> | BTreeSet<infer S>
+      ? CreateInterface<S>[]
+      : any)
+
+// Wrapper for CreateInterface_NoOption that includes resolving an Option
+// (nested Options like Option<Option<Codec>> will resolve to Option<any>, but there are very edge case)
+type CreateInterface<T extends Codec> =
+  | T
+  | (T extends Option<infer S> ? undefined | null | S | CreateInterface_NoOption<S> : CreateInterface_NoOption<T>)
+
 export function createType<TypeName extends keyof InterfaceTypes>(
   type: TypeName,
-  value: any
+  value: InterfaceTypes[TypeName] extends Codec ? CreateInterface<InterfaceTypes[TypeName]> : any
 ): InterfaceTypes[TypeName] {
   return registry.createType(type, value)
 }