diff --git a/.changeset/config.json b/.changeset/config.json index 14706f57..93f8c7fc 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -7,5 +7,5 @@ { "repo": "tien/reactive-dot" } ], "privatePackages": false, - "linked": [["@reactive-dot/core", "@reactive-dot/react"]] + "linked": [["@reactive-dot/core", "@reactive-dot/react", "@reactive-dot/vue"]] } diff --git a/.changeset/modern-lamps-act.md b/.changeset/modern-lamps-act.md new file mode 100644 index 00000000..0642caf1 --- /dev/null +++ b/.changeset/modern-lamps-act.md @@ -0,0 +1,7 @@ +--- +"@reactive-dot/core": minor +"@reactive-dot/utils": minor +"@reactive-dot/vue": minor +--- + +Added Vue integration. diff --git a/apps/docs/docusaurus.config.ts b/apps/docs/docusaurus.config.ts index 5da7929b..2b2326b2 100644 --- a/apps/docs/docusaurus.config.ts +++ b/apps/docs/docusaurus.config.ts @@ -37,6 +37,7 @@ const config: Config = { }, }, "packages/react", + "packages/vue", "packages/utils", ], }, diff --git a/examples/react/package.json b/examples/react/package.json index cb6213bd..b049a646 100644 --- a/examples/react/package.json +++ b/examples/react/package.json @@ -3,11 +3,11 @@ "private": true, "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --port 5173", "build": "vite build", "lint": "eslint src", "preview": "vite preview", - "postinstall": "npx papi" + "postinstall": "papi" }, "dependencies": { "@polkadot-api/descriptors": "portal:.papi/descriptors", diff --git a/examples/react/src/mutation.tsx b/examples/react/src/mutation.tsx index 84a04a61..d37e56ee 100644 --- a/examples/react/src/mutation.tsx +++ b/examples/react/src/mutation.tsx @@ -53,11 +53,7 @@ export function Mutation() { ) : (

Remark

-

diff --git a/examples/vue/.gitignore b/examples/vue/.gitignore new file mode 100644 index 00000000..1521c8b7 --- /dev/null +++ b/examples/vue/.gitignore @@ -0,0 +1 @@ +dist diff --git a/examples/vue/.papi/descriptors/.gitignore b/examples/vue/.papi/descriptors/.gitignore new file mode 100644 index 00000000..557cc814 --- /dev/null +++ b/examples/vue/.papi/descriptors/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!package.json diff --git a/examples/vue/.papi/descriptors/package.json b/examples/vue/.papi/descriptors/package.json new file mode 100644 index 00000000..be9c9382 --- /dev/null +++ b/examples/vue/.papi/descriptors/package.json @@ -0,0 +1,24 @@ +{ + "version": "0.1.0-autogenerated.2857293709118977602", + "name": "@polkadot-api/descriptors", + "files": [ + "dist" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "module": "./dist/index.mjs", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./package.json": "./package.json" + }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "browser": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "sideEffects": false, + "peerDependencies": { + "polkadot-api": "*" + } +} diff --git a/examples/vue/.papi/metadata/kusama.scale b/examples/vue/.papi/metadata/kusama.scale new file mode 100644 index 00000000..47a38b7c Binary files /dev/null and b/examples/vue/.papi/metadata/kusama.scale differ diff --git a/examples/vue/.papi/metadata/polkadot.scale b/examples/vue/.papi/metadata/polkadot.scale new file mode 100644 index 00000000..a950183b Binary files /dev/null and b/examples/vue/.papi/metadata/polkadot.scale differ diff --git a/examples/vue/.papi/metadata/westend.scale b/examples/vue/.papi/metadata/westend.scale new file mode 100644 index 00000000..01291018 Binary files /dev/null and b/examples/vue/.papi/metadata/westend.scale differ diff --git a/examples/vue/.papi/polkadot-api.json b/examples/vue/.papi/polkadot-api.json new file mode 100644 index 00000000..2de6a303 --- /dev/null +++ b/examples/vue/.papi/polkadot-api.json @@ -0,0 +1,18 @@ +{ + "version": 0, + "descriptorPath": ".papi/descriptors", + "entries": { + "polkadot": { + "chain": "polkadot", + "metadata": ".papi/metadata/polkadot.scale" + }, + "kusama": { + "chain": "ksmcc3", + "metadata": ".papi/metadata/kusama.scale" + }, + "westend": { + "chain": "westend2", + "metadata": ".papi/metadata/westend.scale" + } + } +} diff --git a/examples/vue/eslint.config.js b/examples/vue/eslint.config.js new file mode 100644 index 00000000..2bb30664 --- /dev/null +++ b/examples/vue/eslint.config.js @@ -0,0 +1,4 @@ +import recommended from "@reactive-dot/eslint-config/vue.js"; +import tseslint from "typescript-eslint"; + +export default tseslint.config(...recommended); diff --git a/examples/vue/index.html b/examples/vue/index.html new file mode 100644 index 00000000..c39e135c --- /dev/null +++ b/examples/vue/index.html @@ -0,0 +1,12 @@ + + + + + + ReactiveDOT demo + + +

+ + + diff --git a/examples/vue/package.json b/examples/vue/package.json new file mode 100644 index 00000000..86036941 --- /dev/null +++ b/examples/vue/package.json @@ -0,0 +1,27 @@ +{ + "name": "@reactive-dot/example-vue", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port 5174", + "build": "vite build", + "lint": "eslint src", + "preview": "vite preview", + "postinstall": "papi" + }, + "dependencies": { + "@polkadot-api/descriptors": "portal:.papi/descriptors", + "@reactive-dot/vue": "workspace:^", + "polkadot-api": "^1.4.1", + "vue": "^3.5.12" + }, + "devDependencies": { + "@reactive-dot/eslint-config": "workspace:^", + "@tsconfig/recommended": "^1.0.7", + "@tsconfig/strictest": "^2.0.5", + "@vitejs/plugin-vue": "^5.1.4", + "eslint": "^9.12.0", + "typescript": "^5.6.2", + "vite": "^5.4.8" + } +} diff --git a/examples/vue/src/app.vue b/examples/vue/src/app.vue new file mode 100644 index 00000000..db4a7aa1 --- /dev/null +++ b/examples/vue/src/app.vue @@ -0,0 +1,58 @@ + + + diff --git a/examples/vue/src/config.ts b/examples/vue/src/config.ts new file mode 100644 index 00000000..82c45c91 --- /dev/null +++ b/examples/vue/src/config.ts @@ -0,0 +1,43 @@ +import { kusama, polkadot, westend } from "@polkadot-api/descriptors"; +import type { Config } from "@reactive-dot/core"; +import { InjectedWalletAggregator } from "@reactive-dot/core/wallets.js"; +import { getSmProvider } from "polkadot-api/sm-provider"; +import { startFromWorker } from "polkadot-api/smoldot/from-worker"; + +const smoldotPromise = startFromWorker( + new Worker(new URL("polkadot-api/smoldot/worker", import.meta.url), { + type: "module", + }), +); + +export const config = { + chains: { + polkadot: { + descriptor: polkadot, + provider: getSmProvider( + import("polkadot-api/chains/polkadot").then(({ chainSpec }) => + smoldotPromise.addChain({ chainSpec }), + ), + ), + }, + kusama: { + descriptor: kusama, + provider: getSmProvider( + import("polkadot-api/chains/ksmcc3").then(({ chainSpec }) => + smoldotPromise.addChain({ chainSpec }), + ), + ), + }, + westend: { + descriptor: westend, + provider: getSmProvider( + import("polkadot-api/chains/westend2").then(({ chainSpec }) => + smoldotPromise.addChain({ chainSpec }), + ), + ), + }, + }, + wallets: [ + new InjectedWalletAggregator({ originName: "ReactiveDOT Vue Example" }), + ], +} as const satisfies Config; diff --git a/examples/vue/src/index.ts b/examples/vue/src/index.ts new file mode 100644 index 00000000..4427536e --- /dev/null +++ b/examples/vue/src/index.ts @@ -0,0 +1,6 @@ +import App from "./app.vue"; +import { config } from "./config.js"; +import { ReactiveDotPlugin } from "@reactive-dot/vue"; +import { createApp } from "vue"; + +createApp(App).use(ReactiveDotPlugin, config).mount("#root"); diff --git a/examples/vue/src/mutation-section.vue b/examples/vue/src/mutation-section.vue new file mode 100644 index 00000000..d9a91d9e --- /dev/null +++ b/examples/vue/src/mutation-section.vue @@ -0,0 +1,51 @@ + + + diff --git a/examples/vue/src/query-section.vue b/examples/vue/src/query-section.vue new file mode 100644 index 00000000..de85ebd8 --- /dev/null +++ b/examples/vue/src/query-section.vue @@ -0,0 +1,35 @@ + + + diff --git a/examples/vue/src/reactive-dot.d.ts b/examples/vue/src/reactive-dot.d.ts new file mode 100644 index 00000000..9ee45608 --- /dev/null +++ b/examples/vue/src/reactive-dot.d.ts @@ -0,0 +1,7 @@ +import type { config } from "./config.js"; +import type { InferChains } from "@reactive-dot/core"; + +declare module "@reactive-dot/core" { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type + export interface Chains extends InferChains {} +} diff --git a/examples/vue/src/vite-env.d.ts b/examples/vue/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/examples/vue/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/src/wallet-connection.vue b/examples/vue/src/wallet-connection.vue new file mode 100644 index 00000000..7c405725 --- /dev/null +++ b/examples/vue/src/wallet-connection.vue @@ -0,0 +1,46 @@ + + + diff --git a/examples/vue/tsconfig.json b/examples/vue/tsconfig.json new file mode 100644 index 00000000..5638365d --- /dev/null +++ b/examples/vue/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": [ + "@tsconfig/recommended/tsconfig.json", + "@tsconfig/strictest/tsconfig.json" + ], + "compilerOptions": { + "target": "ESNext", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "exactOptionalPropertyTypes": false + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/vue/tsconfig.node.json b/examples/vue/tsconfig.node.json new file mode 100644 index 00000000..97ede7ee --- /dev/null +++ b/examples/vue/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/vue/vite.config.ts b/examples/vue/vite.config.ts new file mode 100644 index 00000000..307689bf --- /dev/null +++ b/examples/vue/vite.config.ts @@ -0,0 +1,6 @@ +import vue from "@vitejs/plugin-vue"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [vue()], +}); diff --git a/nx.json b/nx.json index 0a3a995c..1a453cf8 100644 --- a/nx.json +++ b/nx.json @@ -18,5 +18,5 @@ "cache": true } }, - "parallel": 7 + "parallel": 9 } diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index 34e0efe7..b2370b44 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -1,6 +1,9 @@ +export type { MutationEvent } from "./mutation-event.js"; export { type InferQueryPayload, type InferQueryResponse, type MultiInstruction, type QueryInstruction, } from "./query-builder.js"; +export type { Falsy, FalsyGuard, FlatHead } from "./types.js"; +export { flatHead, maybeThen, stringify, toObservable } from "./utils.js"; diff --git a/packages/core/src/mutation-event.ts b/packages/core/src/mutation-event.ts new file mode 100644 index 00000000..d8b05956 --- /dev/null +++ b/packages/core/src/mutation-event.ts @@ -0,0 +1,13 @@ +import type { ChainId } from "./config.js"; +import type { Transaction } from "polkadot-api"; + +export type MutationEvent = { + id: `${string}-${string}-${string}-${string}-${string}`; + chainId: ChainId; + call?: Transaction< + NonNullable, + string, + string, + unknown + >["decodedCall"]; +}; diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 6e03bd5c..34dfa758 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,5 +1,18 @@ import type { Observable } from "rxjs"; +export type Falsy = undefined | null | false; + +export type FalsyGuard< + TType, + TReturnType, + TFallback = undefined, + TFalsyValues = Falsy, +> = TType extends TFalsyValues ? TReturnType | TFallback : TReturnType; + +export type FlatHead = TArray extends [infer Head] + ? Head + : TArray; + export type Gettable = T | PromiseLike | (() => T | PromiseLike); export type MaybeAsync = T | Promise | Observable; diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 9b940bcd..e89ec164 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -1,5 +1,74 @@ import { from, isObservable, of, type Observable } from "rxjs"; +function hasObjectPrototype(o: unknown) { + return Object.prototype.toString.call(o) === "[object Object]"; +} + +function isPlainObject(value: unknown): value is object { + if (!hasObjectPrototype(value)) { + return false; + } + + // If has modified constructor + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const ctor = (value as any).constructor; + if (typeof ctor === "undefined") return true; + + // If has modified prototype + const prot = ctor.prototype; + if (!hasObjectPrototype(prot)) return false; + + // If constructor does not have an Object-specific method + // eslint-disable-next-line no-prototype-builtins + if (!prot.hasOwnProperty("isPrototypeOf")) return false; + + // Most likely a plain Object + return true; +} + +export function stringify(queryInstruction: T) { + return JSON.stringify(queryInstruction, (_, value) => { + if (typeof value === "bigint") { + return value.toString(); + } + + if (isPlainObject(value)) { + return Object.keys(value) + .sort() + .reduce( + (result, key) => { + result[key] = value[key as keyof typeof value]; + return result; + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + {} as any, + ); + } + + return value; + }); +} + +export function flatHead(value: T): T extends [infer Head] ? Head : T { + if (Array.isArray(value) && value.length === 1) { + return value.at(0); + } + + // @ts-expect-error TODO: fix this + return value; +} + +export function maybeThen( + maybePromise: TValueIn | Promise, + then: (value: TValueIn) => TValueOut, +) { + if (maybePromise instanceof Promise) { + return maybePromise.then(then); + } + + return then(maybePromise); +} + export function toObservable(value: T) { type Value = T extends Observable diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 061a09a8..72e3a514 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -11,6 +11,7 @@ "@types/eslint__js": "^8.42.3", "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-tsdoc": "^0.3.0", + "eslint-plugin-vue": "^9.29.0", "typescript": "^5.6.3", "typescript-eslint": "^8.9.0" }, diff --git a/packages/eslint-config/vue.js b/packages/eslint-config/vue.js new file mode 100644 index 00000000..fe732120 --- /dev/null +++ b/packages/eslint-config/vue.js @@ -0,0 +1,19 @@ +import base from "./index.js"; +import pluginVue from "eslint-plugin-vue"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + ...base, + ...pluginVue.configs["flat/recommended"], + { + languageOptions: { + parserOptions: { + parser: "@typescript-eslint/parser", + }, + }, + rules: { + "vue/max-attributes-per-line": "off", + "vue/singleline-html-element-content-newline": "off", + }, + }, +); diff --git a/packages/react/src/contexts/mutation.tsx b/packages/react/src/contexts/mutation.tsx index a24b224d..5370362d 100644 --- a/packages/react/src/contexts/mutation.tsx +++ b/packages/react/src/contexts/mutation.tsx @@ -1,22 +1,10 @@ -import type { - AsyncValue, - ChainId, - idle, - MutationError, -} from "@reactive-dot/core"; -import type { Transaction, TxEvent } from "polkadot-api"; +import type { AsyncValue, idle, MutationError } from "@reactive-dot/core"; +import type { MutationEvent as BaseMutationEvent } from "@reactive-dot/core/internal.js"; +import type { TxEvent } from "polkadot-api"; import { createContext } from "react"; import { Subject } from "rxjs"; -export type MutationEvent = { - id: `${string}-${string}-${string}-${string}-${string}`; - chainId: ChainId; - call?: Transaction< - NonNullable, - string, - string, - unknown - >["decodedCall"]; +export type MutationEvent = BaseMutationEvent & { value: Exclude, typeof idle>; }; diff --git a/packages/react/src/hooks/use-config.ts b/packages/react/src/hooks/use-config.ts index d5879551..ef372c37 100644 --- a/packages/react/src/hooks/use-config.ts +++ b/packages/react/src/hooks/use-config.ts @@ -13,5 +13,6 @@ export function useConfig() { if (config === undefined) { throw new ReactiveDotError("No config provided"); } + return config; } diff --git a/packages/react/src/hooks/use-query-refresher.ts b/packages/react/src/hooks/use-query-refresher.ts index 7faf4743..e21ffe82 100644 --- a/packages/react/src/hooks/use-query-refresher.ts +++ b/packages/react/src/hooks/use-query-refresher.ts @@ -1,5 +1,4 @@ import { getQueryInstructionPayloadAtoms } from "../stores/query.js"; -import type { Falsy } from "../types.js"; import type { ChainHookOptions } from "./types.js"; import { internal_useChainId } from "./use-chain-id.js"; import { useConfig } from "./use-config.js"; @@ -9,7 +8,7 @@ import { type Chains, type CommonDescriptor, } from "@reactive-dot/core"; -import type { QueryInstruction } from "@reactive-dot/core/internal.js"; +import type { Falsy, QueryInstruction } from "@reactive-dot/core/internal.js"; import { useAtomCallback } from "jotai/utils"; import { useCallback } from "react"; diff --git a/packages/react/src/hooks/use-query.ts b/packages/react/src/hooks/use-query.ts index 3f15a875..49eb67e9 100644 --- a/packages/react/src/hooks/use-query.ts +++ b/packages/react/src/hooks/use-query.ts @@ -1,6 +1,4 @@ import { queryPayloadAtom } from "../stores/query.js"; -import type { Falsy, FalsyGuard, FlatHead } from "../types.js"; -import { flatHead, stringify } from "../utils/vanilla.js"; import type { ChainHookOptions } from "./types.js"; import { internal_useChainId } from "./use-chain-id.js"; import { useConfig } from "./use-config.js"; @@ -12,9 +10,14 @@ import { type Chains, type CommonDescriptor, } from "@reactive-dot/core"; -import type { - InferQueryPayload, - QueryInstruction, +import { + flatHead, + stringify, + type Falsy, + type FalsyGuard, + type FlatHead, + type InferQueryPayload, + type QueryInstruction, } from "@reactive-dot/core/internal.js"; import { atom, useAtomValue } from "jotai"; import { useMemo } from "react"; diff --git a/packages/react/src/stores/query.ts b/packages/react/src/stores/query.ts index d9d4b116..e2734ade 100644 --- a/packages/react/src/stores/query.ts +++ b/packages/react/src/stores/query.ts @@ -1,5 +1,4 @@ import { atomFamilyWithErrorCatcher } from "../utils/jotai.js"; -import { stringify } from "../utils/vanilla.js"; import { typedApiAtom } from "./client.js"; import { preflight, @@ -8,9 +7,10 @@ import { type Config, type Query, } from "@reactive-dot/core"; -import type { - MultiInstruction, - QueryInstruction, +import { + stringify, + type MultiInstruction, + type QueryInstruction, } from "@reactive-dot/core/internal.js"; import { atom, type Atom, type WritableAtom } from "jotai"; import { atomWithObservable, atomWithRefresh } from "jotai/utils"; diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts deleted file mode 100644 index a51ec73a..00000000 --- a/packages/react/src/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -export type Falsy = undefined | null | false; - -export type FalsyGuard< - TType, - TReturnType, - TFallback = undefined, - TFalsyValues = Falsy, -> = TType extends TFalsyValues ? TReturnType | TFallback : TReturnType; - -export type FlatHead = TArray extends [infer Head] - ? Head - : TArray; diff --git a/packages/react/src/utils/vanilla.ts b/packages/react/src/utils/vanilla.ts deleted file mode 100644 index a936c90c..00000000 --- a/packages/react/src/utils/vanilla.ts +++ /dev/null @@ -1,57 +0,0 @@ -function hasObjectPrototype(o: unknown) { - return Object.prototype.toString.call(o) === "[object Object]"; -} - -function isPlainObject(value: unknown): value is object { - if (!hasObjectPrototype(value)) { - return false; - } - - // If has modified constructor - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const ctor = (value as any).constructor; - if (typeof ctor === "undefined") return true; - - // If has modified prototype - const prot = ctor.prototype; - if (!hasObjectPrototype(prot)) return false; - - // If constructor does not have an Object-specific method - // eslint-disable-next-line no-prototype-builtins - if (!prot.hasOwnProperty("isPrototypeOf")) return false; - - // Most likely a plain Object - return true; -} - -export function stringify(queryInstruction: T) { - return JSON.stringify(queryInstruction, (_, value) => { - if (typeof value === "bigint") { - return value.toString(); - } - - if (isPlainObject(value)) { - return Object.keys(value) - .sort() - .reduce( - (result, key) => { - result[key] = value[key as keyof typeof value]; - return result; - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - {} as any, - ); - } - - return value; - }); -} - -export function flatHead(value: T): T extends [infer Head] ? Head : T { - if (Array.isArray(value) && value.length === 1) { - return value.at(0); - } - - // @ts-expect-error TODO: fix this - return value; -} diff --git a/packages/vue/.gitignore b/packages/vue/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/packages/vue/.gitignore @@ -0,0 +1 @@ +build diff --git a/packages/vue/eslint.config.js b/packages/vue/eslint.config.js new file mode 100644 index 00000000..2bb30664 --- /dev/null +++ b/packages/vue/eslint.config.js @@ -0,0 +1,4 @@ +import recommended from "@reactive-dot/eslint-config/vue.js"; +import tseslint from "typescript-eslint"; + +export default tseslint.config(...recommended); diff --git a/packages/vue/package.json b/packages/vue/package.json new file mode 100644 index 00000000..c156e158 --- /dev/null +++ b/packages/vue/package.json @@ -0,0 +1,47 @@ +{ + "name": "@reactive-dot/vue", + "version": "0.0.0", + "description": "Vue library for Reactive DOT", + "keywords": [ + "substrate", + "polkadot", + "vue" + ], + "homepage": "https://reactivedot.dev/", + "bugs": { + "url": "https://github.com/tien/reactive-dot/issues", + "email": "tien.nguyenkhac@icloud.com" + }, + "license": "LGPL-3.0-or-later", + "author": "Tiến Nguyễn Khắc (https://tien.zone/)", + "repository": { + "type": "git", + "url": "https://github.com/tien/reactive-dot.git", + "directory": "packages/vue" + }, + "type": "module", + "files": [ + "src", + "build" + ], + "exports": "./build/index.js", + "scripts": { + "dev": "tsc --build --watch", + "build": "rm -rf build && tsc --build", + "lint": "eslint src" + }, + "dependencies": { + "@reactive-dot/core": "workspace:^" + }, + "devDependencies": { + "@reactive-dot/eslint-config": "workspace:^", + "@tsconfig/recommended": "^1.0.7", + "@tsconfig/strictest": "^2.0.5", + "eslint": "^9.10.0", + "typescript": "^5.6.3", + "vue": "^3.5.6" + }, + "peerDependencies": { + "vue": "3.x" + } +} diff --git a/packages/vue/src/composables/use-accounts.ts b/packages/vue/src/composables/use-accounts.ts new file mode 100644 index 00000000..e9bd8385 --- /dev/null +++ b/packages/vue/src/composables/use-accounts.ts @@ -0,0 +1,29 @@ +import type { ChainComposableOptions } from "../types.js"; +import { useAsyncData } from "./use-async-data.js"; +import { internal_useChainId } from "./use-chain-id.js"; +import { useChainSpecPromise } from "./use-chain-spec-data.js"; +import { useLazyValue } from "./use-lazy-value.js"; +import { useConnectedWalletsObservable } from "./use-wallets.js"; +import { getAccounts } from "@reactive-dot/core"; +import { computed } from "vue"; + +/** + * Composable for getting currently connected accounts. + * + * @param options - Additional options + * @returns The currently connected accounts + */ +export function useAccounts(options?: ChainComposableOptions) { + return useAsyncData(useAccountsPromise(options)); +} + +function useAccountsPromise(options?: ChainComposableOptions) { + const chainId = internal_useChainId(options); + const connectedWalletsObservable = useConnectedWalletsObservable(); + const chainSpecPromise = useChainSpecPromise(options); + + return useLazyValue( + computed(() => ["accounts", chainId.value]), + () => getAccounts(connectedWalletsObservable.value, chainSpecPromise.value), + ); +} diff --git a/packages/vue/src/composables/use-async-action.ts b/packages/vue/src/composables/use-async-action.ts new file mode 100644 index 00000000..de6fd2ba --- /dev/null +++ b/packages/vue/src/composables/use-async-action.ts @@ -0,0 +1,51 @@ +import { useAsyncState } from "./use-async-state.js"; +import { MutationError } from "@reactive-dot/core"; +import type { Observable } from "rxjs"; + +/** + * @internal + */ +export function useAsyncAction< + TActionArgs extends unknown[], + TActionResult extends Promise | Observable, +>(action: (...args: TActionArgs) => TActionResult) { + type Value = + TActionResult extends Promise + ? Value + : TActionResult extends Observable + ? Value + : never; + + const state = useAsyncState(); + + return { + ...state, + execute: (...args: TActionArgs) => { + try { + state.status.value = "pending"; + + const result = action(...args); + + const resolve = (value: unknown) => { + state.data.value = value as Value; + state.status.value = "success"; + }; + + const reject = (reason: unknown) => { + state.error.value = MutationError.from(reason); + state.status.value = "error"; + }; + + if (result instanceof Promise) { + return result.then(resolve).catch(reject); + } else { + return result.subscribe({ next: resolve, error: reject }); + } + } catch (error: unknown) { + state.error.value = MutationError.from(error); + state.status.value = "error"; + throw error; + } + }, + }; +} diff --git a/packages/vue/src/composables/use-async-data.ts b/packages/vue/src/composables/use-async-data.ts new file mode 100644 index 00000000..49dffd7a --- /dev/null +++ b/packages/vue/src/composables/use-async-data.ts @@ -0,0 +1,105 @@ +import type { AsyncState } from "../types.js"; +import { refresh } from "../utils/refreshable.js"; +import { useAsyncState } from "./use-async-state.js"; +import type { lazyValue } from "./use-lazy-value.js"; +import type { Falsy } from "@reactive-dot/core/internal.js"; +import type { Observable, Subscription } from "rxjs"; +import { onWatcherCleanup, shallowReadonly, toValue, watchEffect } from "vue"; + +/** + * @internal + */ +export function useAsyncData< + T extends Promise | Observable | Falsy, +>(future: ReturnType>) { + type Value = + T extends Promise + ? Value + : T extends Observable + ? Value + : never; + + const state = useAsyncState(); + + watchEffect(() => { + const promiseOrObservable = toValue(future); + + if (!promiseOrObservable) { + return; + } + + let abortController: AbortController | undefined; + let subscription: Subscription | undefined; + + state.status.value = "pending"; + + if (promiseOrObservable instanceof Promise) { + abortController = new AbortController(); + + promiseOrObservable + .then((value) => { + if (abortController!.signal.aborted) { + return; + } + + state.data.value = value as Value; + state.status.value = "success"; + }) + .catch((error) => { + if (abortController!.signal.aborted) { + return; + } + + state.error.value = error; + state.status.value = "error"; + }); + } else { + subscription = promiseOrObservable.subscribe({ + next: (value) => { + state.data.value = value as Value; + state.status.value = "success"; + }, + error: (error) => { + state.error.value = error; + state.status.value = "error"; + }, + }); + } + + onWatcherCleanup(() => { + abortController?.abort(); + subscription?.unsubscribe(); + }); + }); + + const returnState = { + data: shallowReadonly(state.data), + error: shallowReadonly(state.error), + status: shallowReadonly(state.status), + refresh: () => refresh(future), + } as AsyncState; + + const { + promise: promiseLike, + resolve, + reject, + } = Promise.withResolvers>(); + + watchEffect(() => { + switch (returnState.status.value) { + case "success": + resolve(returnState); + break; + case "error": + reject(returnState.error.value); + } + }); + + return { + ...returnState, + then: ( + onfulfilled: () => unknown, + onrejected: (reason: unknown) => unknown, + ) => promiseLike.then(onfulfilled, onrejected), + } as AsyncState & PromiseLike>; +} diff --git a/packages/vue/src/composables/use-async-state.ts b/packages/vue/src/composables/use-async-state.ts new file mode 100644 index 00000000..bf1546f6 --- /dev/null +++ b/packages/vue/src/composables/use-async-state.ts @@ -0,0 +1,13 @@ +import type { MutableAsyncState } from "../types.js"; +import { shallowRef } from "vue"; + +/** + * @internal + */ +export function useAsyncState() { + return { + data: shallowRef(), + error: shallowRef(), + status: shallowRef("idle"), + } as MutableAsyncState; +} diff --git a/packages/vue/src/composables/use-block.ts b/packages/vue/src/composables/use-block.ts new file mode 100644 index 00000000..e001bf74 --- /dev/null +++ b/packages/vue/src/composables/use-block.ts @@ -0,0 +1,33 @@ +import type { ChainComposableOptions } from "../types.js"; +import { useAsyncData } from "./use-async-data.js"; +import { internal_useChainId } from "./use-chain-id.js"; +import { useClientPromise } from "./use-client.js"; +import { useLazyValue } from "./use-lazy-value.js"; +import { getBlock } from "@reactive-dot/core"; +import { from, switchMap } from "rxjs"; +import { computed, type MaybeRefOrGetter, toValue } from "vue"; + +/** + * Composable for fetching information about the latest block. + * + * @param tag - Which block to target + * @param options - Additional options + * @returns The latest finalized or best block + */ +export function useBlock( + tag: MaybeRefOrGetter<"best" | "finalized"> = "finalized", + options?: ChainComposableOptions, +) { + const chainId = internal_useChainId(options); + const client = useClientPromise(); + + return useAsyncData( + useLazyValue( + computed(() => ["block", chainId.value, toValue(tag)]), + () => + from(client.value).pipe( + switchMap((client) => getBlock(client, { tag: toValue(tag) })), + ), + ), + ); +} diff --git a/packages/vue/src/composables/use-chain-id.ts b/packages/vue/src/composables/use-chain-id.ts new file mode 100644 index 00000000..578a7153 --- /dev/null +++ b/packages/vue/src/composables/use-chain-id.ts @@ -0,0 +1,72 @@ +import { chainIdKey } from "../keys.js"; +import type { ChainComposableOptions } from "../types.js"; +import { useConfig } from "./use-config.js"; +import { type ChainId, ReactiveDotError } from "@reactive-dot/core"; +import { computed, inject, toValue } from "vue"; + +/** + * Composable for getting all configured chain IDs. + * + * @returns All configured chain IDs + */ +export function useChainIds() { + return computed(() => Object.keys(toValue(useConfig()).chains) as ChainId[]); +} + +/** + * Composable for getting the current chain ID. + * + * @param options - Additional options + * @returns The current chain ID + */ +export function useChainId< + const TAllowList extends ChainId[], + const TDenylist extends ChainId[] = [], +>(options?: { allowlist?: TAllowList; denylist?: TDenylist }) { + const injectedChainId = inject(chainIdKey); + + return computed(() => { + const chainId = toValue(injectedChainId); + + if (chainId === undefined) { + throw new ReactiveDotError("No chain ID provided"); + } + + if (options?.allowlist?.includes(chainId) === false) { + throw new ReactiveDotError("Chain ID not allowed", { cause: chainId }); + } + + if (options?.denylist?.includes(chainId)) { + throw new ReactiveDotError("Chain ID denied", { cause: chainId }); + } + + return chainId as Exclude< + Extract, + TDenylist[number] + >; + }); +} + +/** + * @internal + */ +export function internal_useChainId({ + optionalChainId = false as TOptionalChainId, + ...options +}: ChainComposableOptions & { + optionalChainId?: TOptionalChainId; +} = {}) { + const injectedChainId = inject(chainIdKey); + + return computed(() => { + const chainId = options?.chainId ?? toValue(injectedChainId); + + if (!optionalChainId && chainId === undefined) { + throw new ReactiveDotError("No chain ID provided"); + } + + return chainId as TOptionalChainId extends false + ? ChainId + : ChainId | undefined; + }); +} diff --git a/packages/vue/src/composables/use-chain-spec-data.ts b/packages/vue/src/composables/use-chain-spec-data.ts new file mode 100644 index 00000000..872e2626 --- /dev/null +++ b/packages/vue/src/composables/use-chain-spec-data.ts @@ -0,0 +1,29 @@ +import type { ChainComposableOptions } from "../types.js"; +import { useAsyncData } from "./use-async-data.js"; +import { internal_useChainId } from "./use-chain-id.js"; +import { useClientPromise } from "./use-client.js"; +import { useLazyValue } from "./use-lazy-value.js"; +import { computed } from "vue"; + +/** + * Composable for fetching the [JSON-RPC spec](https://paritytech.github.io/json-rpc-interface-spec/api/chainSpec.html). + * + * @param options - Additional options + * @returns The [JSON-RPC spec](https://paritytech.github.io/json-rpc-interface-spec/api/chainSpec.html) + */ +export function useChainSpecData(options?: ChainComposableOptions) { + return useAsyncData(useChainSpecPromise(options)); +} + +/** + * @internal + */ +export function useChainSpecPromise(options?: ChainComposableOptions) { + const chainId = internal_useChainId(options); + const clientPromise = useClientPromise(options); + + return useLazyValue( + computed(() => ["chain-spec", chainId.value]), + () => clientPromise.value.then((client) => client.getChainSpecData()), + ); +} diff --git a/packages/vue/src/composables/use-client.ts b/packages/vue/src/composables/use-client.ts new file mode 100644 index 00000000..6d75353b --- /dev/null +++ b/packages/vue/src/composables/use-client.ts @@ -0,0 +1,30 @@ +import type { ChainComposableOptions } from "../types.js"; +import { useAsyncData } from "./use-async-data.js"; +import { internal_useChainId } from "./use-chain-id.js"; +import { useChainConfig } from "./use-config.js"; +import { useLazyValue } from "./use-lazy-value.js"; +import { getClient } from "@reactive-dot/core"; +import { computed, toValue } from "vue"; + +/** + * Composable for getting Polkadot-API client instance. + * + * @param options - Additional options + * @returns Polkadot-API client + */ +export function useClient(options?: ChainComposableOptions) { + return useAsyncData(useClientPromise(options)); +} + +/** + * @internal + */ +export function useClientPromise(options?: ChainComposableOptions) { + const chainId = internal_useChainId(options); + const chainConfig = useChainConfig(options); + + return useLazyValue( + computed(() => ["client", chainId.value]), + () => getClient(toValue(chainConfig)), + ); +} diff --git a/packages/vue/src/composables/use-config.ts b/packages/vue/src/composables/use-config.ts new file mode 100644 index 00000000..db8c119e --- /dev/null +++ b/packages/vue/src/composables/use-config.ts @@ -0,0 +1,43 @@ +import { configKey } from "../keys.js"; +import type { ChainComposableOptions } from "../types.js"; +import { useChainId } from "./use-chain-id.js"; +import { ReactiveDotError } from "@reactive-dot/core"; +import { computed, inject, toValue } from "vue"; + +/** + * Composable for getting the current config. + * + * @returns The current config + */ +export function useConfig() { + const config = inject(configKey); + + return computed(() => { + if (config === undefined) { + throw new ReactiveDotError("No config provided"); + } + + return toValue(config); + }); +} + +/** + * @internal + */ +export function useChainConfig(options?: ChainComposableOptions) { + const config = useConfig(); + const chainId = useChainId(); + + return computed(() => { + const chainConfig = + config.value.chains[toValue(options?.chainId) ?? chainId.value]; + + if (chainConfig === undefined) { + throw new ReactiveDotError("No chain's config found", { + cause: options?.chainId ?? chainId.value, + }); + } + + return chainConfig; + }); +} diff --git a/packages/vue/src/composables/use-lazy-value.ts b/packages/vue/src/composables/use-lazy-value.ts new file mode 100644 index 00000000..da58c362 --- /dev/null +++ b/packages/vue/src/composables/use-lazy-value.ts @@ -0,0 +1,90 @@ +import { lazyValuesKey } from "../keys.js"; +import { refreshable } from "../utils/refreshable.js"; +import { ReactiveDotError } from "@reactive-dot/core"; +import { catchError, isObservable } from "rxjs"; +import { + type MaybeRefOrGetter, + type ShallowRef, + computed, + inject, + shallowRef, + toValue, +} from "vue"; + +type Key = string | number; + +/** + * @internal + */ +export function useLazyValue(key: MaybeRefOrGetter, get: () => T) { + return lazyValue(key, get, useLazyValuesCache()); +} + +/** + * @internal + */ +export function useLazyValuesCache() { + const cache = inject(lazyValuesKey); + + if (cache === undefined) { + throw new ReactiveDotError("No lazy values cache provided"); + } + + return cache; +} + +/** + * @internal + */ +export const erroredSymbol = Symbol("errored"); + +export function lazyValue( + key: MaybeRefOrGetter, + get: () => T, + cache: MaybeRefOrGetter>>, +) { + const put = (force = false) => { + const stringKey = toValue(key).join("/"); + + const hasValue = toValue(cache).has(stringKey); + + const refValue = ( + hasValue + ? toValue(cache).get(stringKey)! + : toValue(cache).set(stringKey, shallowRef()).get(stringKey)! + ) as ShallowRef; + + const tagAsErrored = () => + Object.assign(refValue, { [erroredSymbol]: true }); + + if (!hasValue || force) { + try { + refValue.value = get(); + } catch (error) { + tagAsErrored(); + throw error; + } + } + + if (refValue.value instanceof Promise) { + refValue.value = refValue.value.catch((error) => { + tagAsErrored(); + throw error; + }) as T; + } else if (isObservable(refValue.value)) { + refValue.value = refValue.value.pipe( + catchError((error) => { + tagAsErrored(); + throw error; + }), + ) as T; + } + + return refValue.value; + }; + + return refreshable( + computed(() => put()), + () => void put(true), + ); +} diff --git a/packages/vue/src/composables/use-mutation.ts b/packages/vue/src/composables/use-mutation.ts new file mode 100644 index 00000000..23d6cc32 --- /dev/null +++ b/packages/vue/src/composables/use-mutation.ts @@ -0,0 +1,120 @@ +import { mutationEventKey } from "../keys.js"; +import type { ChainComposableOptions } from "../types.js"; +import { useAsyncAction } from "./use-async-action.js"; +import { useChainId } from "./use-chain-id.js"; +import { useSigner } from "./use-signer.js"; +import { useTypedApiPromise } from "./use-typed-api.js"; +import type { ChainId, Chains } from "@reactive-dot/core"; +import { MutationError } from "@reactive-dot/core"; +import type { + PolkadotSigner, + Transaction, + TxObservable, + TypedApi, +} from "polkadot-api"; +import { from } from "rxjs"; +import { catchError, switchMap, tap } from "rxjs/operators"; +import { inject, type MaybeRefOrGetter, toValue } from "vue"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type TxOptions> = Parameters< + TxObservable< + T extends Transaction + ? Asset + : void + > +>[1]; + +/** + * Composable for sending transactions to chains. + * + * @param action - The function to create the transaction + * @param options - Additional options + * @returns The current transaction state & submit function + */ +export function useMutation< + TAction extends ( + builder: TypedApi["tx"], + ) => // eslint-disable-next-line @typescript-eslint/no-explicit-any + Transaction, + TChainId extends ChainId, +>( + action: TAction, + options?: ChainComposableOptions & { + /** + * Override default signer + */ + signer?: MaybeRefOrGetter; + /** + * Additional transaction options + */ + txOptions?: MaybeRefOrGetter> | undefined>; + }, +) { + const injectedSigner = useSigner(); + const typedApiPromise = useTypedApiPromise(); + const chainId = useChainId(); + + const mutationEventRef = inject( + mutationEventKey, + () => { + throw new Error("No mutation event ref provided"); + }, + true, + ); + + return useAsyncAction( + ( + submitOptions?: TxOptions> & { + signer: PolkadotSigner; + }, + ) => { + const signer = + submitOptions?.signer ?? toValue(options?.signer) ?? injectedSigner; + + if (signer === undefined) { + throw new MutationError("No signer provided"); + } + + const id = globalThis.crypto.randomUUID(); + + return from(typedApiPromise.value).pipe( + switchMap((typedApi) => { + const transaction = action(typedApi.tx); + + const eventProps = { + id, + chainId: chainId.value, + call: transaction.decodedCall, + }; + + mutationEventRef.value = { ...eventProps, status: "pending" }; + + return transaction + .signSubmitAndWatch( + signer, + submitOptions ?? toValue(options?.txOptions), + ) + .pipe( + tap( + (value) => + (mutationEventRef.value = { + ...eventProps, + status: "success", + data: value, + }), + ), + catchError((error) => { + mutationEventRef.value = { + ...eventProps, + status: "error", + error: MutationError.from(error), + }; + throw error; + }), + ); + }), + ); + }, + ); +} diff --git a/packages/vue/src/composables/use-query-error-resetter.ts b/packages/vue/src/composables/use-query-error-resetter.ts new file mode 100644 index 00000000..08016fe6 --- /dev/null +++ b/packages/vue/src/composables/use-query-error-resetter.ts @@ -0,0 +1,18 @@ +import { erroredSymbol, useLazyValuesCache } from "./use-lazy-value.js"; +import { toValue } from "vue"; + +/** + * Composable for getting the function to reset all query errors. + * + * @returns Function to reset caught query error + */ +export function useQueryErrorResetter() { + const cache = useLazyValuesCache(); + + return () => + toValue(cache).forEach((value, key) => { + if (erroredSymbol in value) { + toValue(cache).delete(key); + } + }); +} diff --git a/packages/vue/src/composables/use-query.ts b/packages/vue/src/composables/use-query.ts new file mode 100644 index 00000000..e4f8b881 --- /dev/null +++ b/packages/vue/src/composables/use-query.ts @@ -0,0 +1,189 @@ +import type { AsyncState, ChainComposableOptions } from "../types.js"; +import { refresh, refreshable } from "../utils/refreshable.js"; +import { useAsyncData } from "./use-async-data.js"; +import { internal_useChainId } from "./use-chain-id.js"; +import { lazyValue, useLazyValuesCache } from "./use-lazy-value.js"; +import { useTypedApiPromise } from "./use-typed-api.js"; +import { + type ChainId, + type Chains, + type CommonDescriptor, + query as executeQuery, + preflight, + Query, + type QueryError, +} from "@reactive-dot/core"; +import { + type Falsy, + type FlatHead, + flatHead, + type InferQueryPayload, + type MultiInstruction, + type QueryInstruction, + stringify, +} from "@reactive-dot/core/internal.js"; +import type { ChainDefinition, TypedApi } from "polkadot-api"; +import { combineLatest, from, type Observable, of } from "rxjs"; +import { map, switchMap } from "rxjs/operators"; +import { + computed, + type MaybeRefOrGetter, + type ShallowRef, + toValue, + type UnwrapRef, +} from "vue"; + +/** + * Composable for querying data from chain, and returning the response. + * + * @param builder - The function to create the query + * @param options - Additional options + * @returns The data response + */ +export function useQuery< + TQuery extends ( + builder: Query<[], TDescriptor>, + ) => Query[], TDescriptor> | Falsy, + TDescriptor extends TChainId extends void + ? CommonDescriptor + : Chains[TChainId], + TChainId extends ChainId, +>(builder: TQuery, options?: ChainComposableOptions) { + const chainId = internal_useChainId(options); + const typedApiPromise = useTypedApiPromise(options); + const cache = useLazyValuesCache(); + + const responses = computed(() => { + const query = builder(new Query([])); + + if (!query) { + return; + } + + if (query.instructions.length === 1) { + return [ + queryInstruction( + query.instructions.at(0)!, + chainId, + typedApiPromise, + cache, + ), + ]; + } + + return query.instructions.map((instruction) => { + if (!("multi" in instruction)) { + return queryInstruction(instruction, chainId, typedApiPromise, cache); + } + + return (instruction.args as unknown[]).map((args) => { + const { multi, ...rest } = instruction; + return queryInstruction( + { ...rest, args }, + chainId, + typedApiPromise, + cache, + ); + }); + }); + }); + + type Data = FlatHead< + InferQueryPayload< + Exclude, Falsy>>, Falsy> + > + >; + + type Return = + Data extends Array + ? AsyncState & + PromiseLike> + : AsyncState & PromiseLike>; + + return useAsyncData( + refreshable( + computed(() => { + if (responses.value === undefined) { + return; + } + + return combineLatest( + responses.value.map((response) => { + if (!Array.isArray(response)) { + return from(response.value); + } + + const responses = response.map((response) => response.value); + + if (responses.length === 0) { + return of([]); + } + + return combineLatest(response.map((response) => response.value)); + }), + ).pipe(map(flatHead)); + }), + () => { + if (!responses.value) { + return; + } + + if (!Array.isArray(responses.value)) { + return void refresh(responses.value); + } + + for (const response of responses.value) { + if (!Array.isArray(response)) { + refresh(response); + } else { + for (const subResponse of response) { + refresh(subResponse); + } + } + } + }, + ), + ) as Return; +} + +function queryInstruction( + instruction: MaybeRefOrGetter< + Exclude< + QueryInstruction, + MultiInstruction + > + >, + chainId: MaybeRefOrGetter, + typedApiPromise: MaybeRefOrGetter>>, + cache: MaybeRefOrGetter>>, +) { + return lazyValue( + computed(() => [ + "query", + toValue(chainId), + stringify(toValue(instruction)), + ]), + () => { + switch (preflight(toValue(instruction))) { + case "promise": + return toValue(typedApiPromise).then( + (typedApi) => + executeQuery(typedApi, toValue(instruction)) as Promise, + ); + case "observable": + return from(toValue(typedApiPromise)).pipe( + switchMap( + (typedApi) => + executeQuery( + typedApi, + toValue(instruction), + ) as Observable, + ), + ); + } + }, + cache, + ); +} diff --git a/packages/vue/src/composables/use-signer.ts b/packages/vue/src/composables/use-signer.ts new file mode 100644 index 00000000..45c919e1 --- /dev/null +++ b/packages/vue/src/composables/use-signer.ts @@ -0,0 +1,11 @@ +import { signerKey } from "../keys.js"; +import { inject, toValue } from "vue"; + +/** + * Composable for getting the current signer. + * + * @returns The current signer + */ +export function useSigner() { + return toValue(inject(signerKey)); +} diff --git a/packages/vue/src/composables/use-typed-api.ts b/packages/vue/src/composables/use-typed-api.ts new file mode 100644 index 00000000..e9dcd067 --- /dev/null +++ b/packages/vue/src/composables/use-typed-api.ts @@ -0,0 +1,43 @@ +import type { ChainComposableOptions } from "../types.js"; +import { useAsyncData } from "./use-async-data.js"; +import { internal_useChainId } from "./use-chain-id.js"; +import { useClientPromise } from "./use-client.js"; +import { useChainConfig } from "./use-config.js"; +import { useLazyValue } from "./use-lazy-value.js"; +import type { ChainId, Chains } from "@reactive-dot/core"; +import type { TypedApi } from "polkadot-api"; +import { computed } from "vue"; + +/** + * Composable for getting Polkadot-API typed API. + * + * @param options - Additional options + * @returns Polkadot-API typed API + */ +export function useTypedApi( + options?: ChainComposableOptions, +) { + return useAsyncData(useTypedApiPromise(options)); +} + +/** + * @internal + */ +export function useTypedApiPromise( + options?: ChainComposableOptions, +) { + const chainId = internal_useChainId(options); + const chainConfig = useChainConfig(options); + const clientPromise = useClientPromise(options); + + return useLazyValue( + computed(() => ["typed-api", chainId.value]), + () => + clientPromise.value.then( + (client) => + client.getTypedApi(chainConfig.value.descriptor) as TypedApi< + Chains[TChainId] + >, + ), + ); +} diff --git a/packages/vue/src/composables/use-wallet-connector.ts b/packages/vue/src/composables/use-wallet-connector.ts new file mode 100644 index 00000000..922d50c4 --- /dev/null +++ b/packages/vue/src/composables/use-wallet-connector.ts @@ -0,0 +1,26 @@ +import { useAsyncAction } from "./use-async-action.js"; +import { useWalletsObservable } from "./use-wallets.js"; +import { connectWallet } from "@reactive-dot/core"; +import type { Wallet } from "@reactive-dot/core/wallets.js"; +import { firstValueFrom } from "rxjs"; + +/** + * Composable for connecting wallets + * + * @param wallets - Wallets to connect to, will connect to all available wallets if none is specified + * @returns The wallet connection state & connect function + */ +export function useWalletConnector(wallets?: Wallet | Wallet[]) { + const composableWallets = wallets; + + const walletsObservable = useWalletsObservable(); + + return useAsyncAction(async (wallets?: Wallet | Wallet[]) => { + const walletsToConnect = + wallets ?? + composableWallets ?? + (await firstValueFrom(walletsObservable.value)); + + await connectWallet(walletsToConnect); + }); +} diff --git a/packages/vue/src/composables/use-wallet-disconnector.ts b/packages/vue/src/composables/use-wallet-disconnector.ts new file mode 100644 index 00000000..1a59b816 --- /dev/null +++ b/packages/vue/src/composables/use-wallet-disconnector.ts @@ -0,0 +1,26 @@ +import { useAsyncAction } from "./use-async-action.js"; +import { useConnectedWalletsObservable } from "./use-wallets.js"; +import { disconnectWallet } from "@reactive-dot/core"; +import type { Wallet } from "@reactive-dot/core/wallets.js"; +import { firstValueFrom } from "rxjs"; + +/** + * Composable for disconnecting wallets + * + * @param wallets - Wallets to disconnect from, will disconnect from all connected wallets if none is specified + * @returns The wallet disconnection state & disconnect function + */ +export function useWalletDisconnector(wallets?: Wallet | Wallet[]) { + const composableWallets = wallets; + + const connectedWalletsObservable = useConnectedWalletsObservable(); + + return useAsyncAction(async (wallets?: Wallet | Wallet[]) => { + const walletsToDisconnect = + wallets ?? + composableWallets ?? + (await firstValueFrom(connectedWalletsObservable.value)); + + await disconnectWallet(walletsToDisconnect); + }); +} diff --git a/packages/vue/src/composables/use-wallets-initializer.ts b/packages/vue/src/composables/use-wallets-initializer.ts new file mode 100644 index 00000000..b2fda758 --- /dev/null +++ b/packages/vue/src/composables/use-wallets-initializer.ts @@ -0,0 +1,17 @@ +import { useAsyncAction } from "./use-async-action.js"; +import { useWalletsObservable } from "./use-wallets.js"; +import { initializeWallets } from "@reactive-dot/core/wallets.js"; +import { firstValueFrom } from "rxjs"; + +/** + * Composable for initializing wallets. + * + * @returns The initialization state and initialize function + */ +export function useWalletsInitializer() { + const walletsObservable = useWalletsObservable(); + + return useAsyncAction(async () => + initializeWallets(await firstValueFrom(walletsObservable.value)), + ); +} diff --git a/packages/vue/src/composables/use-wallets.ts b/packages/vue/src/composables/use-wallets.ts new file mode 100644 index 00000000..cf2e7363 --- /dev/null +++ b/packages/vue/src/composables/use-wallets.ts @@ -0,0 +1,55 @@ +import { useAsyncData } from "./use-async-data.js"; +import { useConfig } from "./use-config.js"; +import { useLazyValue } from "./use-lazy-value.js"; +import { aggregateWallets, getConnectedWallets } from "@reactive-dot/core"; +import { Wallet, WalletAggregator } from "@reactive-dot/core/wallets.js"; +import { map } from "rxjs/operators"; + +/** + * Composable for getting all available wallets. + * + * @returns Available wallets + */ +export function useWallets() { + return useAsyncData(useWalletsObservable()); +} + +/** + * @internal + */ +export function useWalletsObservable() { + const config = useConfig(); + + return useLazyValue(["wallets"], () => + aggregateWallets( + config.value.wallets?.filter( + (wallet) => wallet instanceof WalletAggregator, + ) ?? [], + ).pipe( + map((aggregatedWallets) => [ + ...(config.value.wallets?.filter( + (wallet) => wallet instanceof Wallet, + ) ?? []), + ...aggregatedWallets, + ]), + ), + ); +} + +/** + * Composable for getting all connected wallets. + * + * @returns Connected wallets + */ +export function useConnectedWallets() { + return useAsyncData(useConnectedWalletsObservable()); +} + +/** + * @internal + */ +export function useConnectedWalletsObservable() { + return useLazyValue(["connected-wallets"], () => + getConnectedWallets(useWalletsObservable().value), + ); +} diff --git a/packages/vue/src/composables/watch-mutation-effect.ts b/packages/vue/src/composables/watch-mutation-effect.ts new file mode 100644 index 00000000..a79d8d4f --- /dev/null +++ b/packages/vue/src/composables/watch-mutation-effect.ts @@ -0,0 +1,16 @@ +import { mutationEventKey } from "../keys.js"; +import type { MutationEvent } from "../types.js"; +import { inject, watch } from "vue"; + +/** + * Watch for mutation events. + * + * @param effect - Callback when new mutation event is emitted + */ +export function watchMutationEffect(effect: (event: MutationEvent) => void) { + watch(inject(mutationEventKey)!, (event) => { + if (event !== undefined) { + effect(event); + } + }); +} diff --git a/packages/vue/src/development.d.ts b/packages/vue/src/development.d.ts new file mode 100644 index 00000000..9751cfc1 --- /dev/null +++ b/packages/vue/src/development.d.ts @@ -0,0 +1,7 @@ +import type { ChainDefinition } from "polkadot-api"; + +declare module "@reactive-dot/core" { + export interface Chains { + [id: string]: ChainDefinition; + } +} diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts new file mode 100644 index 00000000..33efd4f6 --- /dev/null +++ b/packages/vue/src/index.ts @@ -0,0 +1,18 @@ +export { useAccounts } from "./composables/use-accounts.js"; +export { useBlock } from "./composables/use-block.js"; +export { useChainId, useChainIds } from "./composables/use-chain-id.js"; +export { useChainSpecData } from "./composables/use-chain-spec-data.js"; +export { useClient } from "./composables/use-client.js"; +export { useConfig } from "./composables/use-config.js"; +export { useMutation } from "./composables/use-mutation.js"; +export { useQueryErrorResetter } from "./composables/use-query-error-resetter.js"; +export { useQuery } from "./composables/use-query.js"; +export { useSigner } from "./composables/use-signer.js"; +export { useTypedApi } from "./composables/use-typed-api.js"; +export { useWalletConnector } from "./composables/use-wallet-connector.js"; +export { useWalletDisconnector } from "./composables/use-wallet-disconnector.js"; +export { useConnectedWallets, useWallets } from "./composables/use-wallets.js"; +export { watchMutationEffect } from "./composables/watch-mutation-effect.js"; +export { ReactiveDotPlugin } from "./plugin.js"; +export { provideChain } from "./providers/chain.js"; +export { provideSigner } from "./providers/signer.js"; diff --git a/packages/vue/src/keys.ts b/packages/vue/src/keys.ts new file mode 100644 index 00000000..39c8ef60 --- /dev/null +++ b/packages/vue/src/keys.ts @@ -0,0 +1,26 @@ +import type { MutationEvent } from "./types.js"; +import type { PolkadotSigner } from "@polkadot-api/polkadot-signer"; +import type { ChainId, Config } from "@reactive-dot/core"; +import type { InjectionKey, MaybeRefOrGetter, Ref, ShallowRef } from "vue"; + +export const configKey = Symbol() as InjectionKey>; + +export const chainIdKey = Symbol() as InjectionKey>; + +export const signerKey = Symbol() as InjectionKey< + MaybeRefOrGetter +>; + +/** + * @internal + */ +export const lazyValuesKey = Symbol() as InjectionKey< + MaybeRefOrGetter>> +>; + +/** + * @internal + */ +export const mutationEventKey = Symbol() as InjectionKey< + Ref +>; diff --git a/packages/vue/src/plugin.ts b/packages/vue/src/plugin.ts new file mode 100644 index 00000000..a6f9e874 --- /dev/null +++ b/packages/vue/src/plugin.ts @@ -0,0 +1,16 @@ +import { useWalletsInitializer } from "./composables/use-wallets-initializer.js"; +import { configKey, lazyValuesKey, mutationEventKey } from "./keys.js"; +import type { Config } from "@reactive-dot/core"; +import { shallowRef, type Plugin } from "vue"; + +export const ReactiveDotPlugin = { + install(app, config) { + app.provide("foo", "bar"); + app.provide(configKey, config); + app.provide(lazyValuesKey, new Map()); + app.provide(mutationEventKey, shallowRef()); + app.runWithContext(() => { + useWalletsInitializer().execute(); + }); + }, +} satisfies Plugin<[Config]>; diff --git a/packages/vue/src/providers/chain.ts b/packages/vue/src/providers/chain.ts new file mode 100644 index 00000000..4c9a6c42 --- /dev/null +++ b/packages/vue/src/providers/chain.ts @@ -0,0 +1,7 @@ +import { chainIdKey } from "../keys.js"; +import type { ChainId } from "@reactive-dot/core"; +import { provide, type MaybeRefOrGetter } from "vue"; + +export function provideChain(chainId: MaybeRefOrGetter) { + provide(chainIdKey, chainId); +} diff --git a/packages/vue/src/providers/signer.ts b/packages/vue/src/providers/signer.ts new file mode 100644 index 00000000..35e71683 --- /dev/null +++ b/packages/vue/src/providers/signer.ts @@ -0,0 +1,7 @@ +import { signerKey } from "../keys.js"; +import type { PolkadotSigner } from "@polkadot-api/polkadot-signer"; +import { provide, type MaybeRefOrGetter } from "vue"; + +export function provideSigner(signer: MaybeRefOrGetter) { + provide(signerKey, signer); +} diff --git a/packages/vue/src/types.ts b/packages/vue/src/types.ts new file mode 100644 index 00000000..9bf88f0c --- /dev/null +++ b/packages/vue/src/types.ts @@ -0,0 +1,34 @@ +import type { ChainId, MutationError } from "@reactive-dot/core"; +import type { MutationEvent as BaseMutationEvent } from "@reactive-dot/core/internal.js"; +import type { TxEvent } from "polkadot-api"; +import type { MaybeRefOrGetter, Ref } from "vue"; + +export type ChainComposableOptions = { + /** + * Override default chain ID + */ + chainId?: MaybeRefOrGetter; +}; + +type DeepReadonly = { [P in keyof T]: Readonly }; + +export type MutableAsyncState = { + data: Ref; + error: Ref; + status: Ref<"idle" | "pending" | "success" | "error">; +}; + +export type AsyncState< + TData, + TError = unknown, + TDefault = undefined, +> = DeepReadonly> & { + refresh: () => void; +}; + +export type MutationEvent = BaseMutationEvent & + ( + | { status: "pending" } + | { status: "error"; error: MutationError } + | { status: "success"; data: TxEvent } + ); diff --git a/packages/vue/src/utils/refreshable.ts b/packages/vue/src/utils/refreshable.ts new file mode 100644 index 00000000..f8a2f148 --- /dev/null +++ b/packages/vue/src/utils/refreshable.ts @@ -0,0 +1,17 @@ +// TODO: weird TypeScript error +const refreshSymbol = Symbol("refresh") as unknown as "_refresh"; + +type Refreshable = T & { + /** + * @internal + */ + [refreshSymbol]: () => void; +}; + +export function refreshable(value: T, refresh: () => void) { + return Object.assign(value, { [refreshSymbol]: refresh }); +} + +export function refresh(value: Refreshable) { + value[refreshSymbol](); +} diff --git a/packages/vue/tsconfig.json b/packages/vue/tsconfig.json new file mode 100644 index 00000000..eb3ebecf --- /dev/null +++ b/packages/vue/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": [ + "@tsconfig/recommended/tsconfig.json", + "@tsconfig/strictest/tsconfig.json" + ], + "compilerOptions": { + "target": "ESNext", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "build" + }, + "include": ["src"] +} diff --git a/tsconfig.typedoc.json b/tsconfig.typedoc.json index 2e160214..3be74cc3 100644 --- a/tsconfig.typedoc.json +++ b/tsconfig.typedoc.json @@ -2,5 +2,10 @@ "compilerOptions": { "noEmit": true }, - "include": ["packages/core", "packages/react", "packages/utils"] + "include": [ + "packages/core", + "packages/react", + "packages/vue", + "packages/utils" + ] } diff --git a/yarn.lock b/yarn.lock index 9ce317bc..91a9ba73 100644 --- a/yarn.lock +++ b/yarn.lock @@ -575,6 +575,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-string-parser@npm:7.25.7" + checksum: 10c0/73ef2ceb81f8294678a0afe8ab0103729c0370cac2e830e0d5128b03be5f6a2635838af31d391d763e3c5a4460ed96f42fd7c9b552130670d525be665913bc4c + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.16.7, @babel/helper-validator-identifier@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-validator-identifier@npm:7.24.7" @@ -582,6 +589,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-validator-identifier@npm:7.25.7" + checksum: 10c0/07438e5bf01ab2882a15027fdf39ac3b0ba1b251774a5130917907014684e2f70fef8fd620137ca062c4c4eedc388508d2ea7a3a7d9936a32785f4fe116c68c0 + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-validator-option@npm:7.24.7" @@ -660,6 +674,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.25.3": + version: 7.25.8 + resolution: "@babel/parser@npm:7.25.8" + dependencies: + "@babel/types": "npm:^7.25.8" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/a1a13845b7e8dda4c970791814a4bbf60004969882f18f470e260ad822d2e1f8941948f851e9335895563610f240fa6c98481ce8019865e469502bbf21daafa4 + languageName: node + linkType: hard + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.24.7" @@ -1913,6 +1938,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/types@npm:7.25.8" + dependencies: + "@babel/helper-string-parser": "npm:^7.25.7" + "@babel/helper-validator-identifier": "npm:^7.25.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10c0/55ca2d6df6426c98db2769ce884ce5e9de83a512ea2dd7bcf56c811984dc14351cacf42932a723630c5afcff2455809323decd645820762182f10b7b5252b59f + languageName: node + linkType: hard + "@changesets/apply-release-plan@npm:^7.0.5": version: 7.0.5 resolution: "@changesets/apply-release-plan@npm:7.0.5" @@ -4530,6 +4566,42 @@ __metadata: languageName: node linkType: hard +"@polkadot-api/cli@npm:0.9.5": + version: 0.9.5 + resolution: "@polkadot-api/cli@npm:0.9.5" + dependencies: + "@commander-js/extra-typings": "npm:^12.1.0" + "@polkadot-api/codegen": "npm:0.12.3" + "@polkadot-api/json-rpc-provider": "npm:0.0.4" + "@polkadot-api/known-chains": "npm:0.5.4" + "@polkadot-api/metadata-compatibility": "npm:0.1.8" + "@polkadot-api/observable-client": "npm:0.5.8" + "@polkadot-api/polkadot-sdk-compat": "npm:2.2.2" + "@polkadot-api/sm-provider": "npm:0.1.3" + "@polkadot-api/smoldot": "npm:0.3.3" + "@polkadot-api/substrate-bindings": "npm:0.9.1" + "@polkadot-api/substrate-client": "npm:0.2.2" + "@polkadot-api/utils": "npm:0.1.2" + "@polkadot-api/wasm-executor": "npm:^0.1.1" + "@polkadot-api/ws-provider": "npm:0.3.1" + "@types/node": "npm:^22.2.0" + commander: "npm:^12.1.0" + execa: "npm:^9.3.0" + fs.promises.exists: "npm:^1.1.4" + ora: "npm:^8.0.1" + read-pkg: "npm:^9.0.1" + rxjs: "npm:^7.8.1" + tsc-prog: "npm:^2.3.0" + tsup: "npm:^8.2.4" + typescript: "npm:^5.5.4" + write-package: "npm:^7.1.0" + bin: + papi: dist/main.js + polkadot-api: dist/main.js + checksum: 10c0/17474a9d4c9bd5ef86ee9968f9df2c3d734204125c9004c265fa4a146dd5577d3983eb870f3af16b2f1866a8393db81fb3abb0e7d45933c450f63570f0267e72 + languageName: node + linkType: hard + "@polkadot-api/cli@npm:0.9.9": version: 0.9.9 resolution: "@polkadot-api/cli@npm:0.9.9" @@ -4567,6 +4639,18 @@ __metadata: languageName: node linkType: hard +"@polkadot-api/codegen@npm:0.12.3": + version: 0.12.3 + resolution: "@polkadot-api/codegen@npm:0.12.3" + dependencies: + "@polkadot-api/metadata-builders": "npm:0.8.1" + "@polkadot-api/metadata-compatibility": "npm:0.1.8" + "@polkadot-api/substrate-bindings": "npm:0.9.1" + "@polkadot-api/utils": "npm:0.1.2" + checksum: 10c0/b5acd72952a6d18d48ccbbc4310f0f1bf3b4d309a67c12f27bdb3aaf9a2e0ddf8708a36cdfbad412ee3a1aa9c81c2820ccdd83b24286aeb19e5482f535e2c35a + languageName: node + linkType: hard + "@polkadot-api/codegen@npm:0.12.5": version: 0.12.5 resolution: "@polkadot-api/codegen@npm:0.12.5" @@ -4588,6 +4672,14 @@ __metadata: languageName: node linkType: soft +"@polkadot-api/descriptors@portal:.papi/descriptors::locator=%40reactive-dot%2Fexample-vue%40workspace%3Aexamples%2Fvue": + version: 0.0.0-use.local + resolution: "@polkadot-api/descriptors@portal:.papi/descriptors::locator=%40reactive-dot%2Fexample-vue%40workspace%3Aexamples%2Fvue" + peerDependencies: + polkadot-api: "*" + languageName: node + linkType: soft + "@polkadot-api/ink-contracts@npm:0.1.0": version: 0.1.0 resolution: "@polkadot-api/ink-contracts@npm:0.1.0" @@ -4675,6 +4767,16 @@ __metadata: languageName: node linkType: hard +"@polkadot-api/metadata-compatibility@npm:0.1.8": + version: 0.1.8 + resolution: "@polkadot-api/metadata-compatibility@npm:0.1.8" + dependencies: + "@polkadot-api/metadata-builders": "npm:0.8.1" + "@polkadot-api/substrate-bindings": "npm:0.9.1" + checksum: 10c0/f3010703a56b4c4d6361a839a9387ba3a9be55f3c50e1118bfd52b63ad92dea5834ca2615d381f78dde1975de6dcbf9c3ca4004d9a4b14e17c92b656967fed31 + languageName: node + linkType: hard + "@polkadot-api/metadata-compatibility@npm:0.1.9": version: 0.1.9 resolution: "@polkadot-api/metadata-compatibility@npm:0.1.9" @@ -4699,6 +4801,32 @@ __metadata: languageName: node linkType: hard +"@polkadot-api/observable-client@npm:0.5.8": + version: 0.5.8 + resolution: "@polkadot-api/observable-client@npm:0.5.8" + dependencies: + "@polkadot-api/metadata-builders": "npm:0.8.1" + "@polkadot-api/substrate-bindings": "npm:0.9.1" + "@polkadot-api/utils": "npm:0.1.2" + peerDependencies: + "@polkadot-api/substrate-client": 0.2.2 + rxjs: ">=7.8.0" + checksum: 10c0/a485fddca4d1547d544ba7ef7455bafec1f8e023035fc4ac1feb2d6a14e2a31374d201f3fcf23a6efb05006a7d22fb9bfbe42c4b5b9bd5f517414de827c5e70a + languageName: node + linkType: hard + +"@polkadot-api/pjs-signer@npm:0.4.4": + version: 0.4.4 + resolution: "@polkadot-api/pjs-signer@npm:0.4.4" + dependencies: + "@polkadot-api/metadata-builders": "npm:0.8.1" + "@polkadot-api/polkadot-signer": "npm:0.1.6" + "@polkadot-api/substrate-bindings": "npm:0.9.1" + "@polkadot-api/utils": "npm:0.1.2" + checksum: 10c0/556b607ef9d1ed1e0857516035c708e7813bee25446944bec0d280dc925df0b694e76539f8a8b5c9ca20599475b9eec108261c2f8650fa15c97602a9a4ad224b + languageName: node + linkType: hard + "@polkadot-api/pjs-signer@npm:0.5.0": version: 0.5.0 resolution: "@polkadot-api/pjs-signer@npm:0.5.0" @@ -4711,6 +4839,15 @@ __metadata: languageName: node linkType: hard +"@polkadot-api/polkadot-sdk-compat@npm:2.2.2": + version: 2.2.2 + resolution: "@polkadot-api/polkadot-sdk-compat@npm:2.2.2" + dependencies: + "@polkadot-api/json-rpc-provider": "npm:0.0.4" + checksum: 10c0/3b3cecccbdb5cccbc632ecb5d4d66a318a7bbe4fd1e0266b2934b3bbc51a09fe3065b238cd6b1d6b325b11b936c285e4ed7e90d61cf3d37d6d01ba23b086ed0a + languageName: node + linkType: hard + "@polkadot-api/polkadot-sdk-compat@npm:2.3.1": version: 2.3.1 resolution: "@polkadot-api/polkadot-sdk-compat@npm:2.3.1" @@ -4727,6 +4864,17 @@ __metadata: languageName: node linkType: hard +"@polkadot-api/signer@npm:0.1.7": + version: 0.1.7 + resolution: "@polkadot-api/signer@npm:0.1.7" + dependencies: + "@polkadot-api/polkadot-signer": "npm:0.1.6" + "@polkadot-api/substrate-bindings": "npm:0.9.1" + "@polkadot-api/utils": "npm:0.1.2" + checksum: 10c0/7c9ff2e4bc545dbd5303fbf01686a2301c705caca829918353ed45abcc4f6d439e5cc015e0d3479e1d7b25a10ca02c5592ddf731c05f3e991290d6fa52f0db5b + languageName: node + linkType: hard + "@polkadot-api/signer@npm:0.1.8": version: 0.1.8 resolution: "@polkadot-api/signer@npm:0.1.8" @@ -4808,6 +4956,17 @@ __metadata: languageName: node linkType: hard +"@polkadot-api/ws-provider@npm:0.3.1": + version: 0.3.1 + resolution: "@polkadot-api/ws-provider@npm:0.3.1" + dependencies: + "@polkadot-api/json-rpc-provider": "npm:0.0.4" + "@polkadot-api/json-rpc-provider-proxy": "npm:0.2.3" + ws: "npm:^8.18.0" + checksum: 10c0/c2b3d27c9ba769b6378a092a8bb5d799a182cb67eea86265691646da5cd2babb8d5b0075e2daa4bdc24c1663ffbed0249fe61d68352be2a7312b8933d8082106 + languageName: node + linkType: hard + "@polkadot-api/ws-provider@npm:0.3.2": version: 0.3.2 resolution: "@polkadot-api/ws-provider@npm:0.3.2" @@ -4864,6 +5023,7 @@ __metadata: "@types/eslint__js": "npm:^8.42.3" eslint-plugin-react-hooks: "npm:^5.0.0" eslint-plugin-tsdoc: "npm:^0.3.0" + eslint-plugin-vue: "npm:^9.29.0" typescript: "npm:^5.6.3" typescript-eslint: "npm:^8.9.0" peerDependencies: @@ -4899,6 +5059,24 @@ __metadata: languageName: unknown linkType: soft +"@reactive-dot/example-vue@workspace:examples/vue": + version: 0.0.0-use.local + resolution: "@reactive-dot/example-vue@workspace:examples/vue" + dependencies: + "@polkadot-api/descriptors": "portal:.papi/descriptors" + "@reactive-dot/eslint-config": "workspace:^" + "@reactive-dot/vue": "workspace:^" + "@tsconfig/recommended": "npm:^1.0.7" + "@tsconfig/strictest": "npm:^2.0.5" + "@vitejs/plugin-vue": "npm:^5.1.4" + eslint: "npm:^9.12.0" + polkadot-api: "npm:^1.4.1" + typescript: "npm:^5.6.2" + vite: "npm:^5.4.8" + vue: "npm:^3.5.12" + languageName: unknown + linkType: soft + "@reactive-dot/react@workspace:^, @reactive-dot/react@workspace:packages/react": version: 0.0.0-use.local resolution: "@reactive-dot/react@workspace:packages/react" @@ -4931,6 +5109,22 @@ __metadata: languageName: unknown linkType: soft +"@reactive-dot/vue@workspace:^, @reactive-dot/vue@workspace:packages/vue": + version: 0.0.0-use.local + resolution: "@reactive-dot/vue@workspace:packages/vue" + dependencies: + "@reactive-dot/core": "workspace:^" + "@reactive-dot/eslint-config": "workspace:^" + "@tsconfig/recommended": "npm:^1.0.7" + "@tsconfig/strictest": "npm:^2.0.5" + eslint: "npm:^9.10.0" + typescript: "npm:^5.6.3" + vue: "npm:^3.5.6" + peerDependencies: + vue: 3.x + languageName: unknown + linkType: soft + "@reactive-dot/wallet-ledger@workspace:^, @reactive-dot/wallet-ledger@workspace:packages/wallet-ledger": version: 0.0.0-use.local resolution: "@reactive-dot/wallet-ledger@workspace:packages/wallet-ledger" @@ -6313,6 +6507,16 @@ __metadata: languageName: node linkType: hard +"@vitejs/plugin-vue@npm:^5.1.4": + version: 5.1.4 + resolution: "@vitejs/plugin-vue@npm:5.1.4" + peerDependencies: + vite: ^5.0.0 + vue: ^3.2.25 + checksum: 10c0/e5294bfd6d1491bee76091807933769dc49a8e752f17ed50f6894340ffbc53c51ba436ac395df9e7a0a0e446bcde8e5e5ee77411800fb14559f48927bdb86cf3 + languageName: node + linkType: hard + "@vitest/expect@npm:2.1.3": version: 2.1.3 resolution: "@vitest/expect@npm:2.1.3" @@ -6402,6 +6606,106 @@ __metadata: languageName: node linkType: hard +"@vue/compiler-core@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/compiler-core@npm:3.5.12" + dependencies: + "@babel/parser": "npm:^7.25.3" + "@vue/shared": "npm:3.5.12" + entities: "npm:^4.5.0" + estree-walker: "npm:^2.0.2" + source-map-js: "npm:^1.2.0" + checksum: 10c0/7f004b96330c00dc5b94f436be05ce3b196818a8bb1bfeb8f137aba0691deedd20c53e4aa05de830150578af6106e9f306b1fdf973f2db8470e59e81f0fc3a0f + languageName: node + linkType: hard + +"@vue/compiler-dom@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/compiler-dom@npm:3.5.12" + dependencies: + "@vue/compiler-core": "npm:3.5.12" + "@vue/shared": "npm:3.5.12" + checksum: 10c0/48a67cd28c25e94dccff3c1e18bf2f79b073e486a856e5b30661e89e50d08cd49ababc43de94626c948da77c8dad859909e32d3ab678079e90dfa5d3e1ddc344 + languageName: node + linkType: hard + +"@vue/compiler-sfc@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/compiler-sfc@npm:3.5.12" + dependencies: + "@babel/parser": "npm:^7.25.3" + "@vue/compiler-core": "npm:3.5.12" + "@vue/compiler-dom": "npm:3.5.12" + "@vue/compiler-ssr": "npm:3.5.12" + "@vue/shared": "npm:3.5.12" + estree-walker: "npm:^2.0.2" + magic-string: "npm:^0.30.11" + postcss: "npm:^8.4.47" + source-map-js: "npm:^1.2.0" + checksum: 10c0/b897443320c975ee4eb708a6862ab500619879879c8199e344baf5f2788497d26a550baaab6c5c898210155fb0375658faa0cf2a05406f82f765295cb0024f30 + languageName: node + linkType: hard + +"@vue/compiler-ssr@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/compiler-ssr@npm:3.5.12" + dependencies: + "@vue/compiler-dom": "npm:3.5.12" + "@vue/shared": "npm:3.5.12" + checksum: 10c0/8a8fc4e2057fa1292860ff8a53af04604dc70f72aa4dcc7136f1c697adca9bc511ba5ffeca0259b14cc6b18119be726cd784845a6669427774793625d6e953b7 + languageName: node + linkType: hard + +"@vue/reactivity@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/reactivity@npm:3.5.12" + dependencies: + "@vue/shared": "npm:3.5.12" + checksum: 10c0/e088141fec9ac9a136e3275a041a6a18dd18b24e2d76055acaef93d487ed331bc50e6678547a398cda99efebd5325d8b462730f8a1c721a52485e755e9fc95d6 + languageName: node + linkType: hard + +"@vue/runtime-core@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/runtime-core@npm:3.5.12" + dependencies: + "@vue/reactivity": "npm:3.5.12" + "@vue/shared": "npm:3.5.12" + checksum: 10c0/0d79ab1a434c2675f50b542b69bad69d798258b35a4c6884b25be43535fbbaaa439261db0b3d80259c67a5794fc450f2e6d5c1cfbbeabf99ddd66d7a5a66bd59 + languageName: node + linkType: hard + +"@vue/runtime-dom@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/runtime-dom@npm:3.5.12" + dependencies: + "@vue/reactivity": "npm:3.5.12" + "@vue/runtime-core": "npm:3.5.12" + "@vue/shared": "npm:3.5.12" + csstype: "npm:^3.1.3" + checksum: 10c0/ab253c85b9c4f7ee212cdf0bdbfec6ed3d76c3c06b7021ebedb829383651019391286a5fcd041593df3650dc2f1d96f3d7ddac82a3c5b71c22aaafb360e0bc02 + languageName: node + linkType: hard + +"@vue/server-renderer@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/server-renderer@npm:3.5.12" + dependencies: + "@vue/compiler-ssr": "npm:3.5.12" + "@vue/shared": "npm:3.5.12" + peerDependencies: + vue: 3.5.12 + checksum: 10c0/d9f25f165c7d8fd53773238bd53fce4621e61676bbec12a57cd5b29aa4f15d7a2b3e93777665b85666edda57ce7f7deb4504a4596822006684babe0f8d2b41f6 + languageName: node + linkType: hard + +"@vue/shared@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/shared@npm:3.5.12" + checksum: 10c0/48f94406c42921901b21a57a7ebb401bbceb497152baf0554e5d5a11cbaa79958f966042e9d95614c0b02e8681b7e1b6c010fcb8b28c6bda1b090f2ddd7540d8 + languageName: node + linkType: hard + "@walletconnect/core@npm:2.17.1": version: 2.17.1 resolution: "@walletconnect/core@npm:2.17.1" @@ -6974,6 +7278,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.9.0": + version: 8.13.0 + resolution: "acorn@npm:8.13.0" + bin: + acorn: bin/acorn + checksum: 10c0/f35dd53d68177c90699f4c37d0bb205b8abe036d955d0eb011ddb7f14a81e6fd0f18893731c457c1b5bd96754683f4c3d80d9a5585ddecaa53cdf84e0b3d68f7 + languageName: node + linkType: hard + "address@npm:^1.0.1, address@npm:^1.1.2": version: 1.2.2 resolution: "address@npm:1.2.2" @@ -8781,7 +9094,7 @@ __metadata: languageName: node linkType: hard -"csstype@npm:^3.0.10, csstype@npm:^3.0.2": +"csstype@npm:^3.0.10, csstype@npm:^3.0.2, csstype@npm:^3.1.3": version: 3.1.3 resolution: "csstype@npm:3.1.3" checksum: 10c0/80c089d6f7e0c5b2bd83cf0539ab41474198579584fa10d86d0cafe0642202343cbc119e076a0b1aece191989477081415d66c9fefbf3c957fc2fc4b7009f248 @@ -9496,7 +9809,7 @@ __metadata: languageName: node linkType: hard -"entities@npm:^4.2.0, entities@npm:^4.4.0": +"entities@npm:^4.2.0, entities@npm:^4.4.0, entities@npm:^4.5.0": version: 4.5.0 resolution: "entities@npm:4.5.0" checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250 @@ -9948,6 +10261,24 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-vue@npm:^9.29.0": + version: 9.29.0 + resolution: "eslint-plugin-vue@npm:9.29.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.4.0" + globals: "npm:^13.24.0" + natural-compare: "npm:^1.4.0" + nth-check: "npm:^2.1.1" + postcss-selector-parser: "npm:^6.0.15" + semver: "npm:^7.6.3" + vue-eslint-parser: "npm:^9.4.3" + xml-name-validator: "npm:^4.0.0" + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + checksum: 10c0/940cf183d85fefa23eba46cbef4ade7556746867b4f5d18261d84fa1049ee311b800236d390cc29cf46d47488f6895833d4bdb8b14a4848b905e024a769618b9 + languageName: node + linkType: hard + "eslint-scope@npm:5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" @@ -9958,6 +10289,16 @@ __metadata: languageName: node linkType: hard +"eslint-scope@npm:^7.1.1": + version: 7.2.2 + resolution: "eslint-scope@npm:7.2.2" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^5.2.0" + checksum: 10c0/613c267aea34b5a6d6c00514e8545ef1f1433108097e857225fed40d397dd6b1809dffd11c2fde23b37ca53d7bf935fe04d2a18e6fc932b31837b6ad67e1c116 + languageName: node + linkType: hard + "eslint-scope@npm:^8.1.0": version: 8.1.0 resolution: "eslint-scope@npm:8.1.0" @@ -9968,7 +10309,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.3": +"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": version: 3.4.3 resolution: "eslint-visitor-keys@npm:3.4.3" checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 @@ -9989,7 +10330,7 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^9.12.0": +"eslint@npm:^9.10.0, eslint@npm:^9.12.0": version: 9.12.0 resolution: "eslint@npm:9.12.0" dependencies: @@ -10061,6 +10402,17 @@ __metadata: languageName: node linkType: hard +"espree@npm:^9.3.1": + version: 9.6.1 + resolution: "espree@npm:9.6.1" + dependencies: + acorn: "npm:^8.9.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^3.4.1" + checksum: 10c0/1a2e9b4699b715347f62330bcc76aee224390c28bb02b31a3752e9d07549c473f5f986720483c6469cf3cfb3c9d05df612ffc69eb1ee94b54b739e67de9bb460 + languageName: node + linkType: hard + "esprima@npm:^4.0.0": version: 4.0.1 resolution: "esprima@npm:4.0.1" @@ -10071,6 +10423,15 @@ __metadata: languageName: node linkType: hard +"esquery@npm:^1.4.0": + version: 1.6.0 + resolution: "esquery@npm:1.6.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 10c0/cb9065ec605f9da7a76ca6dadb0619dfb611e37a81e318732977d90fab50a256b95fee2d925fba7c2f3f0523aa16f91587246693bc09bc34d5a59575fe6e93d2 + languageName: node + linkType: hard + "esquery@npm:^1.5.0": version: 1.5.0 resolution: "esquery@npm:1.5.0" @@ -10162,6 +10523,13 @@ __metadata: languageName: node linkType: hard +"estree-walker@npm:^2.0.2": + version: 2.0.2 + resolution: "estree-walker@npm:2.0.2" + checksum: 10c0/53a6c54e2019b8c914dc395890153ffdc2322781acf4bd7d1a32d7aedc1710807bdcd866ac133903d5629ec601fbb50abe8c2e5553c7f5a0afdd9b6af6c945af + languageName: node + linkType: hard + "estree-walker@npm:^3.0.0, estree-walker@npm:^3.0.3": version: 3.0.3 resolution: "estree-walker@npm:3.0.3" @@ -11027,6 +11395,15 @@ __metadata: languageName: node linkType: hard +"globals@npm:^13.24.0": + version: 13.24.0 + resolution: "globals@npm:13.24.0" + dependencies: + type-fest: "npm:^0.20.2" + checksum: 10c0/d3c11aeea898eb83d5ec7a99508600fbe8f83d2cf00cbb77f873dbf2bcb39428eff1b538e4915c993d8a3b3473fa71eeebfe22c9bb3a3003d1e26b1f2c8a42cd + languageName: node + linkType: hard + "globals@npm:^14.0.0": version: 14.0.0 resolution: "globals@npm:14.0.0" @@ -14503,7 +14880,7 @@ __metadata: languageName: node linkType: hard -"nth-check@npm:^2.0.1": +"nth-check@npm:^2.0.1, nth-check@npm:^2.1.1": version: 2.1.1 resolution: "nth-check@npm:2.1.1" dependencies: @@ -15259,6 +15636,36 @@ __metadata: languageName: node linkType: hard +"polkadot-api@npm:^1.4.1": + version: 1.4.1 + resolution: "polkadot-api@npm:1.4.1" + dependencies: + "@polkadot-api/cli": "npm:0.9.5" + "@polkadot-api/json-rpc-provider": "npm:0.0.4" + "@polkadot-api/known-chains": "npm:0.5.4" + "@polkadot-api/logs-provider": "npm:0.0.6" + "@polkadot-api/metadata-builders": "npm:0.8.1" + "@polkadot-api/metadata-compatibility": "npm:0.1.8" + "@polkadot-api/observable-client": "npm:0.5.8" + "@polkadot-api/pjs-signer": "npm:0.4.4" + "@polkadot-api/polkadot-sdk-compat": "npm:2.2.2" + "@polkadot-api/polkadot-signer": "npm:0.1.6" + "@polkadot-api/signer": "npm:0.1.7" + "@polkadot-api/sm-provider": "npm:0.1.3" + "@polkadot-api/smoldot": "npm:0.3.3" + "@polkadot-api/substrate-bindings": "npm:0.9.1" + "@polkadot-api/substrate-client": "npm:0.2.2" + "@polkadot-api/utils": "npm:0.1.2" + "@polkadot-api/ws-provider": "npm:0.3.1" + peerDependencies: + rxjs: ">=7.8.0" + bin: + papi: bin/cli.mjs + polkadot-api: bin/cli.mjs + checksum: 10c0/1a7a649997e94397e7dcf5d5d135013c30dd268198ec5b5f8bce4000e4b109aae1bb306892528c090b457c8b9e1da5f5381c78de00c8ec158ac070f71334aa2a + languageName: node + linkType: hard + "polkadot-api@npm:^1.6.1": version: 1.6.1 resolution: "polkadot-api@npm:1.6.1" @@ -15696,6 +16103,16 @@ __metadata: languageName: node linkType: hard +"postcss-selector-parser@npm:^6.0.15": + version: 6.1.2 + resolution: "postcss-selector-parser@npm:6.1.2" + dependencies: + cssesc: "npm:^3.0.0" + util-deprecate: "npm:^1.0.2" + checksum: 10c0/523196a6bd8cf660bdf537ad95abd79e546d54180f9afb165a4ab3e651ac705d0f8b8ce6b3164fb9e3279ce482c5f751a69eb2d3a1e8eb0fd5e82294fb3ef13e + languageName: node + linkType: hard + "postcss-sort-media-queries@npm:^5.2.0": version: 5.2.0 resolution: "postcss-sort-media-queries@npm:5.2.0" @@ -15768,6 +16185,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.4.47": + version: 8.4.47 + resolution: "postcss@npm:8.4.47" + dependencies: + nanoid: "npm:^3.3.7" + picocolors: "npm:^1.1.0" + source-map-js: "npm:^1.2.1" + checksum: 10c0/929f68b5081b7202709456532cee2a145c1843d391508c5a09de2517e8c4791638f71dd63b1898dba6712f8839d7a6da046c72a5e44c162e908f5911f57b5f44 + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -17155,6 +17583,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.3.6, semver@npm:^7.6.3": + version: 7.6.3 + resolution: "semver@npm:7.6.3" + bin: + semver: bin/semver.js + checksum: 10c0/88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf + languageName: node + linkType: hard + "send@npm:0.18.0": version: 0.18.0 resolution: "send@npm:0.18.0" @@ -17571,6 +18008,13 @@ __metadata: languageName: node linkType: hard +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + "source-map-support@npm:~0.5.20": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" @@ -18439,6 +18883,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^0.20.2": + version: 0.20.2 + resolution: "type-fest@npm:0.20.2" + checksum: 10c0/dea9df45ea1f0aaa4e2d3bed3f9a0bfe9e5b2592bddb92eb1bf06e50bcf98dbb78189668cd8bc31a0511d3fc25539b4cd5c704497e53e93e2d40ca764b10bfc3 + languageName: node + linkType: hard + "type-fest@npm:^1.0.1": version: 1.4.0 resolution: "type-fest@npm:1.4.0" @@ -18533,6 +18984,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:^5.6.2": + version: 5.6.2 + resolution: "typescript@npm:5.6.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/3ed8297a8c7c56b7fec282532503d1ac795239d06e7c4966b42d4330c6cf433a170b53bcf93a130a7f14ccc5235de5560df4f1045eb7f3550b46ebed16d3c5e5 + languageName: node + linkType: hard + "typescript@npm:^5.6.3": version: 5.6.3 resolution: "typescript@npm:5.6.3" @@ -18553,6 +19014,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A^5.6.2#optional!builtin": + version: 5.6.2 + resolution: "typescript@patch:typescript@npm%3A5.6.2#optional!builtin::version=5.6.2&hash=8c6c40" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/94eb47e130d3edd964b76da85975601dcb3604b0c848a36f63ac448d0104e93819d94c8bdf6b07c00120f2ce9c05256b8b6092d23cf5cf1c6fa911159e4d572f + languageName: node + linkType: hard + "typescript@patch:typescript@npm%3A^5.6.3#optional!builtin": version: 5.6.3 resolution: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin::version=5.6.3&hash=8c6c40" @@ -19191,6 +19662,49 @@ __metadata: languageName: node linkType: hard +"vite@npm:^5.4.8": + version: 5.4.8 + resolution: "vite@npm:5.4.8" + dependencies: + esbuild: "npm:^0.21.3" + fsevents: "npm:~2.3.3" + postcss: "npm:^8.4.43" + rollup: "npm:^4.20.0" + peerDependencies: + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/af70af6d6316a3af71f44ebe3ab343bd66450d4157af73af3b32239e1b6ec43ff6f651d7cc4193b21ed3bff2e9356a3de9e96aee53857f39922e4a2d9fad75a1 + languageName: node + linkType: hard + "vite@npm:^5.4.9": version: 5.4.9 resolution: "vite@npm:5.4.9" @@ -19297,6 +19811,41 @@ __metadata: languageName: node linkType: hard +"vue-eslint-parser@npm:^9.4.3": + version: 9.4.3 + resolution: "vue-eslint-parser@npm:9.4.3" + dependencies: + debug: "npm:^4.3.4" + eslint-scope: "npm:^7.1.1" + eslint-visitor-keys: "npm:^3.3.0" + espree: "npm:^9.3.1" + esquery: "npm:^1.4.0" + lodash: "npm:^4.17.21" + semver: "npm:^7.3.6" + peerDependencies: + eslint: ">=6.0.0" + checksum: 10c0/128be5988de025b5abd676a91c3e92af68288a5da1c20b2ff848fe90e040c04b2222a03b5d8048cf4a5e0b667a8addfb6f6e6565860d4afb5190c4cc42d05578 + languageName: node + linkType: hard + +"vue@npm:^3.5.12, vue@npm:^3.5.6": + version: 3.5.12 + resolution: "vue@npm:3.5.12" + dependencies: + "@vue/compiler-dom": "npm:3.5.12" + "@vue/compiler-sfc": "npm:3.5.12" + "@vue/runtime-dom": "npm:3.5.12" + "@vue/server-renderer": "npm:3.5.12" + "@vue/shared": "npm:3.5.12" + peerDependencies: + typescript: "*" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/24b2abfe6204f0d41ec5f70388c0cf9764b53be09b913286bdc521ab91dd82b583e13630709ae0956cf9e04fa2489b623d221a34ef609a67315189c12da0be72 + languageName: node + linkType: hard + "watchpack@npm:^2.4.1": version: 2.4.1 resolution: "watchpack@npm:2.4.1" @@ -19762,6 +20311,13 @@ __metadata: languageName: node linkType: hard +"xml-name-validator@npm:^4.0.0": + version: 4.0.0 + resolution: "xml-name-validator@npm:4.0.0" + checksum: 10c0/c1bfa219d64e56fee265b2bd31b2fcecefc063ee802da1e73bad1f21d7afd89b943c9e2c97af2942f60b1ad46f915a4c81e00039c7d398b53cf410e29d3c30bd + languageName: node + linkType: hard + "y18n@npm:^4.0.0": version: 4.0.3 resolution: "y18n@npm:4.0.3"