From 227d87135d7e2e2694632138d34e6e583d13ac5b Mon Sep 17 00:00:00 2001 From: Zetazzz Date: Tue, 11 Apr 2023 07:17:06 +0800 Subject: [PATCH] fix can't get properties when msgs are $ref --- __fixtures__/issues/98/out/98.client.ts | 247 ++++++++ __fixtures__/issues/98/out/98.react-query.ts | 50 ++ __fixtures__/issues/98/out/98.types.ts | 100 ++++ __fixtures__/issues/98/out/bundle.ts | 15 + __fixtures__/issues/98/schema.json | 550 ++++++++++++++++++ .../__tests__/ts-codegen.issue-98.test.ts | 28 + .../ts-client.issue-98.test.ts.snap | 117 ++++ .../client/test/ts-client.issue-98.test.ts | 55 ++ .../wasm-ast-types/src/context/context.ts | 4 +- packages/wasm-ast-types/src/utils/babel.ts | 28 +- packages/wasm-ast-types/src/utils/index.ts | 1 + packages/wasm-ast-types/src/utils/ref.ts | 6 + .../wasm-ast-types/types/recoil/recoil.d.ts | 2 +- .../wasm-ast-types/types/utils/babel.d.ts | 4 +- .../wasm-ast-types/types/utils/index.d.ts | 1 + packages/wasm-ast-types/types/utils/ref.d.ts | 2 + 16 files changed, 1200 insertions(+), 10 deletions(-) create mode 100644 __fixtures__/issues/98/out/98.client.ts create mode 100644 __fixtures__/issues/98/out/98.react-query.ts create mode 100644 __fixtures__/issues/98/out/98.types.ts create mode 100644 __fixtures__/issues/98/out/bundle.ts create mode 100644 __fixtures__/issues/98/schema.json create mode 100644 packages/ts-codegen/__tests__/ts-codegen.issue-98.test.ts create mode 100644 packages/wasm-ast-types/src/client/test/__snapshots__/ts-client.issue-98.test.ts.snap create mode 100644 packages/wasm-ast-types/src/client/test/ts-client.issue-98.test.ts create mode 100644 packages/wasm-ast-types/src/utils/ref.ts create mode 100644 packages/wasm-ast-types/types/utils/ref.d.ts diff --git a/__fixtures__/issues/98/out/98.client.ts b/__fixtures__/issues/98/out/98.client.ts new file mode 100644 index 00000000..b4e37f5b --- /dev/null +++ b/__fixtures__/issues/98/out/98.client.ts @@ -0,0 +1,247 @@ +/** +* This file was automatically generated by @cosmwasm/ts-codegen@latest. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from "@cosmjs/cosmwasm-stargate"; +import { StdFee } from "@cosmjs/amino"; +import { Uint128, InstantiateMsg, Coin, ExecuteMsg, InstallableExecMsg, Binary, ExecMsg, QueryMsg, InstallableQueryMsg, QueryMsg1, ConfigResponse, NullablePlugin, CanonicalAddr, Plugin, PluginsResponse } from "./98.types"; +export interface 98ReadOnlyInterface { + contractAddress: string; + getConfig: () => Promise; + getPlugins: ({ + limit, + startAfter + }: { + limit?: number; + startAfter?: number; + }) => Promise; + getPluginById: ({ + id + }: { + id: number; + }) => Promise; +} +export class 98QueryClient implements 98ReadOnlyInterface { + client: CosmWasmClient; + contractAddress: string; + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client; + this.contractAddress = contractAddress; + this.getConfig = this.getConfig.bind(this); + this.getPlugins = this.getPlugins.bind(this); + this.getPluginById = this.getPluginById.bind(this); + } + + getConfig = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_config: {} + }); + }; + getPlugins = async ({ + limit, + startAfter + }: { + limit?: number; + startAfter?: number; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_plugins: { + limit, + start_after: startAfter + } + }); + }; + getPluginById = async ({ + id + }: { + id: number; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_plugin_by_id: { + id + } + }); + }; +} +export interface 98Interface extends 98ReadOnlyInterface { + contractAddress: string; + sender: string; + proxyInstallPlugin: ({ + id, + instantiateMsg + }: { + id: number; + instantiateMsg: Binary; + }, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; + registerPlugin: ({ + checksum, + codeId, + creator, + ipfsHash, + name, + version + }: { + checksum: string; + codeId: number; + creator: string; + ipfsHash: string; + name: string; + version: string; + }, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; + unregisterPlugin: ({ + id + }: { + id: number; + }, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; + updatePlugin: ({ + checksum, + codeId, + creator, + id, + ipfsHash, + name, + version + }: { + checksum?: string; + codeId?: number; + creator?: string; + id: number; + ipfsHash?: string; + name?: string; + version?: string; + }, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; + updateRegistryFee: ({ + newFee + }: { + newFee: Coin; + }, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; + updateDaoAddr: ({ + newAddr + }: { + newAddr: string; + }, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; +} +export class 98Client extends 98QueryClient implements 98Interface { + client: SigningCosmWasmClient; + sender: string; + contractAddress: string; + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress); + this.client = client; + this.sender = sender; + this.contractAddress = contractAddress; + this.proxyInstallPlugin = this.proxyInstallPlugin.bind(this); + this.registerPlugin = this.registerPlugin.bind(this); + this.unregisterPlugin = this.unregisterPlugin.bind(this); + this.updatePlugin = this.updatePlugin.bind(this); + this.updateRegistryFee = this.updateRegistryFee.bind(this); + this.updateDaoAddr = this.updateDaoAddr.bind(this); + } + + proxyInstallPlugin = async ({ + id, + instantiateMsg + }: { + id: number; + instantiateMsg: Binary; + }, fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + proxy_install_plugin: { + id, + instantiate_msg: instantiateMsg + } + }, fee, memo, funds); + }; + registerPlugin = async ({ + checksum, + codeId, + creator, + ipfsHash, + name, + version + }: { + checksum: string; + codeId: number; + creator: string; + ipfsHash: string; + name: string; + version: string; + }, fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + register_plugin: { + checksum, + code_id: codeId, + creator, + ipfs_hash: ipfsHash, + name, + version + } + }, fee, memo, funds); + }; + unregisterPlugin = async ({ + id + }: { + id: number; + }, fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + unregister_plugin: { + id + } + }, fee, memo, funds); + }; + updatePlugin = async ({ + checksum, + codeId, + creator, + id, + ipfsHash, + name, + version + }: { + checksum?: string; + codeId?: number; + creator?: string; + id: number; + ipfsHash?: string; + name?: string; + version?: string; + }, fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + update_plugin: { + checksum, + code_id: codeId, + creator, + id, + ipfs_hash: ipfsHash, + name, + version + } + }, fee, memo, funds); + }; + updateRegistryFee = async ({ + newFee + }: { + newFee: Coin; + }, fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + update_registry_fee: { + new_fee: newFee + } + }, fee, memo, funds); + }; + updateDaoAddr = async ({ + newAddr + }: { + newAddr: string; + }, fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + update_dao_addr: { + new_addr: newAddr + } + }, fee, memo, funds); + }; +} \ No newline at end of file diff --git a/__fixtures__/issues/98/out/98.react-query.ts b/__fixtures__/issues/98/out/98.react-query.ts new file mode 100644 index 00000000..f9e31110 --- /dev/null +++ b/__fixtures__/issues/98/out/98.react-query.ts @@ -0,0 +1,50 @@ +/** +* This file was automatically generated by @cosmwasm/ts-codegen@latest. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ + +import { UseQueryOptions, useQuery } from "react-query"; +import { Uint128, InstantiateMsg, Coin, ExecuteMsg, InstallableExecMsg, Binary, ExecMsg, QueryMsg, InstallableQueryMsg, QueryMsg1, ConfigResponse, NullablePlugin, CanonicalAddr, Plugin, PluginsResponse } from "./98.types"; +import { 98QueryClient } from "./98.client"; +export interface 98ReactQuery { + client: 98QueryClient; + options?: UseQueryOptions; +} +export interface 98GetPluginByIdQuery extends 98ReactQuery { + args: { + id: number; + }; +} +export function use98GetPluginByIdQuery({ + client, + args, + options +}: 98GetPluginByIdQuery) { + return useQuery(["98GetPluginById", client.contractAddress, JSON.stringify(args)], () => client.getPluginById({ + id: args.id + }), options); +} +export interface 98GetPluginsQuery extends 98ReactQuery { + args: { + limit?: number; + startAfter?: number; + }; +} +export function use98GetPluginsQuery({ + client, + args, + options +}: 98GetPluginsQuery) { + return useQuery(["98GetPlugins", client.contractAddress, JSON.stringify(args)], () => client.getPlugins({ + limit: args.limit, + startAfter: args.startAfter + }), options); +} +export interface 98GetConfigQuery extends 98ReactQuery {} +export function use98GetConfigQuery({ + client, + options +}: 98GetConfigQuery) { + return useQuery(["98GetConfig", client.contractAddress], () => client.getConfig(), options); +} \ No newline at end of file diff --git a/__fixtures__/issues/98/out/98.types.ts b/__fixtures__/issues/98/out/98.types.ts new file mode 100644 index 00000000..3fa99382 --- /dev/null +++ b/__fixtures__/issues/98/out/98.types.ts @@ -0,0 +1,100 @@ +/** +* This file was automatically generated by @cosmwasm/ts-codegen@latest. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ + +export type Uint128 = string; +export interface InstantiateMsg { + install_fee: Coin; + registry_fee: Coin; + [k: string]: unknown; +} +export interface Coin { + amount: Uint128; + denom: string; + [k: string]: unknown; +} +export type ExecuteMsg = InstallableExecMsg | ExecMsg; +export type InstallableExecMsg = { + proxy_install_plugin: { + id: number; + instantiate_msg: Binary; + [k: string]: unknown; + }; +}; +export type Binary = string; +export type ExecMsg = { + register_plugin: { + checksum: string; + code_id: number; + creator: string; + ipfs_hash: string; + name: string; + version: string; + [k: string]: unknown; + }; +} | { + unregister_plugin: { + id: number; + [k: string]: unknown; + }; +} | { + update_plugin: { + checksum?: string | null; + code_id?: number | null; + creator?: string | null; + id: number; + ipfs_hash?: string | null; + name?: string | null; + version?: string | null; + [k: string]: unknown; + }; +} | { + update_registry_fee: { + new_fee: Coin; + [k: string]: unknown; + }; +} | { + update_dao_addr: { + new_addr: string; + [k: string]: unknown; + }; +}; +export type QueryMsg = InstallableQueryMsg | QueryMsg1; +export type InstallableQueryMsg = string; +export type QueryMsg1 = { + get_config: { + [k: string]: unknown; + }; +} | { + get_plugins: { + limit?: number | null; + start_after?: number | null; + [k: string]: unknown; + }; +} | { + get_plugin_by_id: { + id: number; + [k: string]: unknown; + }; +}; +export interface ConfigResponse { + dao_addr: string; + registry_fee: Coin; +} +export type NullablePlugin = Plugin | null; +export type CanonicalAddr = Binary; +export interface Plugin { + checksum: string; + code_id: number; + creator: CanonicalAddr; + id: number; + ipfs_hash: string; + name: string; + version: string; +} +export interface PluginsResponse { + plugins: Plugin[]; + total: number; +} \ No newline at end of file diff --git a/__fixtures__/issues/98/out/bundle.ts b/__fixtures__/issues/98/out/bundle.ts new file mode 100644 index 00000000..4f76a61e --- /dev/null +++ b/__fixtures__/issues/98/out/bundle.ts @@ -0,0 +1,15 @@ +/** +* This file was automatically generated by @cosmwasm/ts-codegen@latest. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ + +import * as _0 from "./98.types"; +import * as _1 from "./98.client"; +import * as _2 from "./98.react-query"; +export namespace contracts { + export const 98 = { ..._0, + ..._1, + ..._2 + }; +} \ No newline at end of file diff --git a/__fixtures__/issues/98/schema.json b/__fixtures__/issues/98/schema.json new file mode 100644 index 00000000..e0f7fdcf --- /dev/null +++ b/__fixtures__/issues/98/schema.json @@ -0,0 +1,550 @@ +{ + "contract_name": "vectis-plugin-registry", + "contract_version": "0.1.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "install_fee", + "registry_fee" + ], + "properties": { + "install_fee": { + "$ref": "#/definitions/Coin" + }, + "registry_fee": { + "$ref": "#/definitions/Coin" + } + }, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "anyOf": [ + { + "$ref": "#/definitions/InstallableExecMsg" + }, + { + "$ref": "#/definitions/ExecMsg" + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "ExecMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "register_plugin" + ], + "properties": { + "register_plugin": { + "type": "object", + "required": [ + "checksum", + "code_id", + "creator", + "ipfs_hash", + "name", + "version" + ], + "properties": { + "checksum": { + "type": "string" + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "creator": { + "type": "string" + }, + "ipfs_hash": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unregister_plugin" + ], + "properties": { + "unregister_plugin": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_plugin" + ], + "properties": { + "update_plugin": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "checksum": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "creator": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "ipfs_hash": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "version": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_registry_fee" + ], + "properties": { + "update_registry_fee": { + "type": "object", + "required": [ + "new_fee" + ], + "properties": { + "new_fee": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_dao_addr" + ], + "properties": { + "update_dao_addr": { + "type": "object", + "required": [ + "new_addr" + ], + "properties": { + "new_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "InstallableExecMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "proxy_install_plugin" + ], + "properties": { + "proxy_install_plugin": { + "type": "object", + "required": [ + "id", + "instantiate_msg" + ], + "properties": { + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "instantiate_msg": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "anyOf": [ + { + "$ref": "#/definitions/InstallableQueryMsg" + }, + { + "$ref": "#/definitions/QueryMsg" + } + ], + "definitions": { + "InstallableQueryMsg": { + "type": "string", + "enum": [] + }, + "QueryMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "get_config" + ], + "properties": { + "get_config": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_plugins" + ], + "properties": { + "get_plugins": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_plugin_by_id" + ], + "properties": { + "get_plugin_by_id": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ] + } + } + }, + "migrate": null, + "sudo": null, + "responses": { + "get_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "type": "object", + "required": [ + "dao_addr", + "registry_fee" + ], + "properties": { + "dao_addr": { + "type": "string" + }, + "registry_fee": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_plugin_by_id": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_Plugin", + "anyOf": [ + { + "$ref": "#/definitions/Plugin" + }, + { + "type": "null" + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "CanonicalAddr": { + "description": "A blockchain address in its binary form.\n\nThe specific implementation is up to the underlying chain and CosmWasm as well as contracts should not make assumptions on that data. In Ethereum for example, an `Addr` would contain a user visible address like 0x14d3cc818735723ab86eaf9502376e847a64ddad and the corresponding `CanonicalAddr` would store the 20 bytes 0x14, 0xD3, ..., 0xAD. In Cosmos, the bech32 format is used for `Addr`s and the `CanonicalAddr` holds the encoded bech32 data without the checksum. Typical sizes are 20 bytes for externally owned addresses and 32 bytes for module addresses (such as x/wasm contract addresses). That being said, a chain might decide to use any size other than 20 or 32 bytes.\n\nThe safe way to obtain a valid `CanonicalAddr` is using `Api::addr_canonicalize`. In addition to that there are many unsafe ways to convert any binary data into an instance. So the type shoud be treated as a marker to express the intended data type, not as a validity guarantee of any sort.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "Plugin": { + "type": "object", + "required": [ + "checksum", + "code_id", + "creator", + "id", + "ipfs_hash", + "name", + "version" + ], + "properties": { + "checksum": { + "type": "string" + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "creator": { + "$ref": "#/definitions/CanonicalAddr" + }, + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "ipfs_hash": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "get_plugins": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginsResponse", + "type": "object", + "required": [ + "plugins", + "total" + ], + "properties": { + "plugins": { + "type": "array", + "items": { + "$ref": "#/definitions/Plugin" + } + }, + "total": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "CanonicalAddr": { + "description": "A blockchain address in its binary form.\n\nThe specific implementation is up to the underlying chain and CosmWasm as well as contracts should not make assumptions on that data. In Ethereum for example, an `Addr` would contain a user visible address like 0x14d3cc818735723ab86eaf9502376e847a64ddad and the corresponding `CanonicalAddr` would store the 20 bytes 0x14, 0xD3, ..., 0xAD. In Cosmos, the bech32 format is used for `Addr`s and the `CanonicalAddr` holds the encoded bech32 data without the checksum. Typical sizes are 20 bytes for externally owned addresses and 32 bytes for module addresses (such as x/wasm contract addresses). That being said, a chain might decide to use any size other than 20 or 32 bytes.\n\nThe safe way to obtain a valid `CanonicalAddr` is using `Api::addr_canonicalize`. In addition to that there are many unsafe ways to convert any binary data into an instance. So the type shoud be treated as a marker to express the intended data type, not as a validity guarantee of any sort.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "Plugin": { + "type": "object", + "required": [ + "checksum", + "code_id", + "creator", + "id", + "ipfs_hash", + "name", + "version" + ], + "properties": { + "checksum": { + "type": "string" + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "creator": { + "$ref": "#/definitions/CanonicalAddr" + }, + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "ipfs_hash": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + } +} \ No newline at end of file diff --git a/packages/ts-codegen/__tests__/ts-codegen.issue-98.test.ts b/packages/ts-codegen/__tests__/ts-codegen.issue-98.test.ts new file mode 100644 index 00000000..f3748160 --- /dev/null +++ b/packages/ts-codegen/__tests__/ts-codegen.issue-98.test.ts @@ -0,0 +1,28 @@ +import { TSBuilder } from '../src/builder'; + +const FIXTURE_DIR = __dirname + '/../../../__fixtures__'; +const OUTPUT_DIR = __dirname + '/../../../__output__'; + +it('issue-98', async () => { + const outPath = FIXTURE_DIR + '/issues/98/out'; + const schemaDir = FIXTURE_DIR + '/issues/98/'; + + const builder = new TSBuilder({ + contracts: [ + schemaDir + ], + outPath, + options: { + types: { + enabled: true + }, + client: { + enabled: true + }, + reactQuery: { + enabled: true + } + } + }); + await builder.build(); +}); \ No newline at end of file diff --git a/packages/wasm-ast-types/src/client/test/__snapshots__/ts-client.issue-98.test.ts.snap b/packages/wasm-ast-types/src/client/test/__snapshots__/ts-client.issue-98.test.ts.snap new file mode 100644 index 00000000..2ec7781f --- /dev/null +++ b/packages/wasm-ast-types/src/client/test/__snapshots__/ts-client.issue-98.test.ts.snap @@ -0,0 +1,117 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`execute classes array types 1`] = ` +"export class SG721Client implements SG721Instance { + client: SigningCosmWasmClient; + sender: string; + contractAddress: string; + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + this.client = client; + this.sender = sender; + this.contractAddress = contractAddress; + this.getConfig = this.getConfig.bind(this); + this.getPlugins = this.getPlugins.bind(this); + this.getPluginById = this.getPluginById.bind(this); + } + + getConfig = async (fee: number | StdFee | \\"auto\\" = \\"auto\\", memo?: string, funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + get_config: {} + }, fee, memo, funds); + }; + getPlugins = async ({ + limit, + startAfter + }: { + limit?: number; + startAfter?: number; + }, fee: number | StdFee | \\"auto\\" = \\"auto\\", memo?: string, funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + get_plugins: { + limit, + start_after: startAfter + } + }, fee, memo, funds); + }; + getPluginById = async ({ + id + }: { + id: number; + }, fee: number | StdFee | \\"auto\\" = \\"auto\\", memo?: string, funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + get_plugin_by_id: { + id + } + }, fee, memo, funds); + }; +}" +`; + +exports[`execute interfaces no extends 1`] = ` +"export interface SG721Instance { + contractAddress: string; + sender: string; + getConfig: (fee?: number | StdFee | \\"auto\\", memo?: string, funds?: Coin[]) => Promise; + getPlugins: ({ + limit, + startAfter + }: { + limit?: number; + startAfter?: number; + }, fee?: number | StdFee | \\"auto\\", memo?: string, funds?: Coin[]) => Promise; + getPluginById: ({ + id + }: { + id: number; + }, fee?: number | StdFee | \\"auto\\", memo?: string, funds?: Coin[]) => Promise; +}" +`; + +exports[`execute_msg_for__empty 1`] = `"export type QueryMsg = QueryMsg;"`; + +exports[`query classes 1`] = ` +"export class SG721QueryClient implements SG721ReadOnlyInstance { + client: CosmWasmClient; + contractAddress: string; + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client; + this.contractAddress = contractAddress; + this.getConfig = this.getConfig.bind(this); + this.getPlugins = this.getPlugins.bind(this); + this.getPluginById = this.getPluginById.bind(this); + } + + getConfig = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_config: {} + }); + }; + getPlugins = async ({ + limit, + startAfter + }: { + limit?: number; + startAfter?: number; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_plugins: { + limit, + start_after: startAfter + } + }); + }; + getPluginById = async ({ + id + }: { + id: number; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_plugin_by_id: { + id + } + }); + }; +}" +`; diff --git a/packages/wasm-ast-types/src/client/test/ts-client.issue-98.test.ts b/packages/wasm-ast-types/src/client/test/ts-client.issue-98.test.ts new file mode 100644 index 00000000..d5fa6905 --- /dev/null +++ b/packages/wasm-ast-types/src/client/test/ts-client.issue-98.test.ts @@ -0,0 +1,55 @@ +import contract from '../../../../../__fixtures__/issues/98/schema.json'; + +import { + createQueryClass, + createExecuteClass, + createExecuteInterface, + createTypeInterface +} from '../client' +import { expectCode, printCode, makeContext } from '../../../test-utils'; + +const message = contract.query +const ctx = makeContext(message); + +it('execute_msg_for__empty', () => { + expectCode(createTypeInterface( + ctx, + message + )) +}) + + +it('query classes', () => { + expectCode(createQueryClass( + ctx, + 'SG721QueryClient', + 'SG721ReadOnlyInstance', + message + )) +}); + +// it('query classes response', () => { +// expectCode(createTypeInterface( +// ctx, +// contract.responses.all_debt_shares +// )) +// }); + +it('execute classes array types', () => { + expectCode(createExecuteClass( + ctx, + 'SG721Client', + 'SG721Instance', + null, + message + )) +}); + +it('execute interfaces no extends', () => { + expectCode(createExecuteInterface( + ctx, + 'SG721Instance', + null, + message + )) +}); diff --git a/packages/wasm-ast-types/src/context/context.ts b/packages/wasm-ast-types/src/context/context.ts index 2b325705..cde2a123 100644 --- a/packages/wasm-ast-types/src/context/context.ts +++ b/packages/wasm-ast-types/src/context/context.ts @@ -1,4 +1,5 @@ import { JSONSchema } from "../types"; +import { refLookup } from "../utils"; import { convertUtilsToImportList, getImportStatements, UtilMapping } from "./imports"; import deepmerge from "deepmerge"; @@ -148,8 +149,7 @@ export abstract class RenderContextBase implements IRender */ abstract mergeDefaultOpt(options: TOpt): TOpt; refLookup($ref: string) { - const refName = $ref.replace('#/definitions/', '') - return this.schema.definitions?.[refName]; + return refLookup($ref, this.schema) } addUtil(util: string) { this.utils[util] = true; diff --git a/packages/wasm-ast-types/src/utils/babel.ts b/packages/wasm-ast-types/src/utils/babel.ts index 5cbe2d8c..8515f192 100644 --- a/packages/wasm-ast-types/src/utils/babel.ts +++ b/packages/wasm-ast-types/src/utils/babel.ts @@ -2,6 +2,7 @@ import * as t from '@babel/types'; import { snake } from "case"; import { Field, QueryMsg, ExecuteMsg } from '../types'; import { TSTypeAnnotation, TSExpressionWithTypeArguments } from '@babel/types'; +import { refLookup } from './ref'; // t.TSPropertySignature - kind? export const propertySignature = ( @@ -30,11 +31,28 @@ export const tsTypeOperator = (typeAnnotation: t.TSType, operator: string) => { return obj; }; -export const getMessageProperties = (msg: QueryMsg | ExecuteMsg) => { - if (msg.anyOf) return msg.anyOf; - if (msg.oneOf) return msg.oneOf; - if (msg.allOf) return msg.allOf; - return []; +export const getMessageProperties = (msg) => { + let results = []; + let objs = []; + if (msg.anyOf) { objs = msg.anyOf; } + else if (msg.oneOf) { objs = msg.oneOf; } + else if (msg.allOf) { objs = msg.allOf; } + + for (const obj of objs) { + if(obj.properties){ + results.push(obj); + } else{ + if(obj.$ref){ + const ref = refLookup(obj.$ref, msg) + + const refProps = getMessageProperties(ref); + + results = [...results, ...refProps]; + } + } + } + + return results; } export const tsPropertySignature = ( diff --git a/packages/wasm-ast-types/src/utils/index.ts b/packages/wasm-ast-types/src/utils/index.ts index d19d5ac7..2e232599 100644 --- a/packages/wasm-ast-types/src/utils/index.ts +++ b/packages/wasm-ast-types/src/utils/index.ts @@ -1,2 +1,3 @@ export * from './babel'; export * from './types'; +export * from './ref'; diff --git a/packages/wasm-ast-types/src/utils/ref.ts b/packages/wasm-ast-types/src/utils/ref.ts new file mode 100644 index 00000000..8c720289 --- /dev/null +++ b/packages/wasm-ast-types/src/utils/ref.ts @@ -0,0 +1,6 @@ +import { JSONSchema } from "../types"; + +export const refLookup = ($ref: string, schema: JSONSchema) => { + const refName = $ref.replace('#/definitions/', '') + return schema.definitions?.[refName]; +} \ No newline at end of file diff --git a/packages/wasm-ast-types/types/recoil/recoil.d.ts b/packages/wasm-ast-types/types/recoil/recoil.d.ts index 03ee4b37..e296e779 100644 --- a/packages/wasm-ast-types/types/recoil/recoil.d.ts +++ b/packages/wasm-ast-types/types/recoil/recoil.d.ts @@ -2,7 +2,7 @@ import * as t from '@babel/types'; import { QueryMsg } from '../types'; import { RenderContext } from '../context'; export declare const createRecoilSelector: (context: RenderContext, keyPrefix: string, QueryClient: string, methodName: string, responseType: string) => t.ExportNamedDeclaration; -export declare const createRecoilSelectors: (context: RenderContext, keyPrefix: string, QueryClient: string, queryMsg: QueryMsg) => any; +export declare const createRecoilSelectors: (context: RenderContext, keyPrefix: string, QueryClient: string, queryMsg: QueryMsg) => t.ExportNamedDeclaration[]; export declare const createRecoilQueryClientType: () => { type: string; id: { diff --git a/packages/wasm-ast-types/types/utils/babel.d.ts b/packages/wasm-ast-types/types/utils/babel.d.ts index 9147f72e..4b9a5919 100644 --- a/packages/wasm-ast-types/types/utils/babel.d.ts +++ b/packages/wasm-ast-types/types/utils/babel.d.ts @@ -1,5 +1,5 @@ import * as t from '@babel/types'; -import { Field, QueryMsg, ExecuteMsg } from '../types'; +import { Field } from '../types'; import { TSTypeAnnotation, TSExpressionWithTypeArguments } from '@babel/types'; export declare const propertySignature: (name: string, typeAnnotation: t.TSTypeAnnotation, optional?: boolean) => { type: string; @@ -9,7 +9,7 @@ export declare const propertySignature: (name: string, typeAnnotation: t.TSTypeA }; export declare const identifier: (name: string, typeAnnotation: t.TSTypeAnnotation, optional?: boolean) => t.Identifier; export declare const tsTypeOperator: (typeAnnotation: t.TSType, operator: string) => t.TSTypeOperator; -export declare const getMessageProperties: (msg: QueryMsg | ExecuteMsg) => any; +export declare const getMessageProperties: (msg: any) => any[]; export declare const tsPropertySignature: (key: t.Expression, typeAnnotation: t.TSTypeAnnotation, optional: boolean) => t.TSPropertySignature; export declare const tsObjectPattern: (properties: (t.RestElement | t.ObjectProperty)[], typeAnnotation: t.TSTypeAnnotation) => t.ObjectPattern; export declare const callExpression: (callee: t.Expression | t.V8IntrinsicIdentifier, _arguments: (t.Expression | t.SpreadElement | t.ArgumentPlaceholder)[], typeParameters: t.TSTypeParameterInstantiation) => t.CallExpression; diff --git a/packages/wasm-ast-types/types/utils/index.d.ts b/packages/wasm-ast-types/types/utils/index.d.ts index d19d5ac7..2e232599 100644 --- a/packages/wasm-ast-types/types/utils/index.d.ts +++ b/packages/wasm-ast-types/types/utils/index.d.ts @@ -1,2 +1,3 @@ export * from './babel'; export * from './types'; +export * from './ref'; diff --git a/packages/wasm-ast-types/types/utils/ref.d.ts b/packages/wasm-ast-types/types/utils/ref.d.ts new file mode 100644 index 00000000..30c8b2c1 --- /dev/null +++ b/packages/wasm-ast-types/types/utils/ref.d.ts @@ -0,0 +1,2 @@ +import { JSONSchema } from "../types"; +export declare const refLookup: ($ref: string, schema: JSONSchema) => JSONSchema;