Merge pull request 'fix: Generic utilities (isRecord, coerceString, getErrorMessage, ensureAddress) have no shared home (#363)' (#429) from fix/issue-363 into master

This commit is contained in:
johba 2026-03-03 08:06:12 +01:00
commit 1f9d11d494
8 changed files with 78 additions and 44 deletions

View file

@ -204,6 +204,19 @@ services:
echo "@harb/web3 linked with wagmi/viem deps"
fi
# Overlay @harb/utils shared package from workspace
if [ -d "$WS/packages/utils" ]; then
mkdir -p /app/packages/utils
cp -r "$WS/packages/utils/." /app/packages/utils/
# Link @harb/utils into web-app node_modules
mkdir -p /app/web-app/node_modules/@harb
ln -sf /app/packages/utils /app/web-app/node_modules/@harb/utils
# Symlink viem into packages dir so @harb/utils can resolve it
mkdir -p /app/packages/utils/node_modules
ln -sf /app/web-app/node_modules/viem /app/packages/utils/node_modules/viem
echo "@harb/utils linked with viem dep"
fi
echo "=== Starting webapp (pre-built image + source overlay) ==="
cd /app/web-app
# Explicitly set CI=true to disable Vue DevTools in vite.config.ts

13
package-lock.json generated
View file

@ -3360,6 +3360,10 @@
"resolved": "packages/ui-shared",
"link": true
},
"node_modules/@harb/utils": {
"resolved": "packages/utils",
"link": true
},
"node_modules/@harb/web3": {
"resolved": "packages/web3",
"link": true
@ -19332,10 +19336,18 @@
"vue": "^3.5.0"
}
},
"packages/utils": {
"name": "@harb/utils",
"version": "0.1.0",
"dependencies": {
"viem": "^2.22.13"
}
},
"packages/web3": {
"name": "@harb/web3",
"version": "0.1.0",
"dependencies": {
"@harb/utils": "*",
"@wagmi/vue": "^0.2.8",
"viem": "^2.22.13"
},
@ -19347,6 +19359,7 @@
"name": "harb-staking",
"version": "0.0.0",
"dependencies": {
"@harb/utils": "*",
"@harb/web3": "*",
"@tanstack/vue-query": "^5.64.2",
"@vue/test-utils": "^2.4.6",

View file

@ -0,0 +1,11 @@
{
"name": "@harb/utils",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts",
"dependencies": {
"viem": "^2.22.13"
}
}

View file

@ -0,0 +1,34 @@
import { getAddress, isAddress, type Address } from 'viem';
export function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}
export function coerceString(value: unknown): string | null {
if (typeof value === 'string') {
const trimmed = value.trim();
if (trimmed.length > 0) return trimmed;
}
return null;
}
export function getErrorMessage(error: unknown, fallback: string): string {
if (error instanceof Error) {
const msg = coerceString(error.message);
if (msg) return msg;
}
if (isRecord(error)) {
const short = coerceString(error.shortMessage);
if (short) return short;
const msg = coerceString(error.message);
if (msg) return msg;
}
return fallback;
}
export function ensureAddress(value: string, label: string): Address {
if (!value || !isAddress(value)) {
throw new Error(`${label} is not a valid address`);
}
return getAddress(value);
}

View file

@ -6,6 +6,7 @@
"main": "src/index.ts",
"types": "src/index.ts",
"dependencies": {
"@harb/utils": "*",
"@wagmi/vue": "^0.2.8",
"viem": "^2.22.13"
},

View file

@ -33,7 +33,8 @@
"vue-router": "^4.2.5",
"vue-tippy": "^6.6.0",
"vue-toastification": "^2.0.0-rc.5",
"@harb/web3": "*"
"@harb/web3": "*",
"@harb/utils": "*"
},
"devDependencies": {
"@iconify/vue": "^4.3.0",

View file

@ -1,11 +1,12 @@
import { computed, ref } from 'vue';
import { useAccount } from '@wagmi/vue';
import { readContract, writeContract } from '@wagmi/core';
import { erc20Abi, getAddress, isAddress, maxUint256, parseEther, zeroAddress, type Abi, type Address } from 'viem';
import { erc20Abi, maxUint256, parseEther, zeroAddress, type Abi } from 'viem';
import { useToast } from 'vue-toastification';
import { config as wagmiConfig } from '@/wagmi';
import { getChain, DEFAULT_CHAIN_ID } from '@/config';
import { useWallet } from '@/composables/useWallet';
import { getErrorMessage, ensureAddress } from '@harb/utils';
export const WETH_ABI = [
{
@ -89,39 +90,6 @@ export const UNISWAP_POOL_ABI = [
},
] as const satisfies Abi;
export function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}
export function coerceString(value: unknown): string | null {
if (typeof value === 'string') {
const trimmed = value.trim();
if (trimmed.length > 0) return trimmed;
}
return null;
}
export function getErrorMessage(error: unknown, fallback: string): string {
if (error instanceof Error) {
const msg = coerceString(error.message);
if (msg) return msg;
}
if (isRecord(error)) {
const short = coerceString(error.shortMessage);
if (short) return short;
const msg = coerceString(error.message);
if (msg) return msg;
}
return fallback;
}
export function ensureAddress(value: string, label: string): Address {
if (!value || !isAddress(value)) {
throw new Error(`${label} is not a valid address`);
}
return getAddress(value);
}
export function useSwapKrk() {
const toast = useToast();
const { address, chainId } = useAccount();

View file

@ -151,15 +151,8 @@ import {
type Abi,
type EIP1193Provider,
} from 'viem';
import {
SWAP_ROUTER_ABI,
UNISWAP_FACTORY_ABI,
UNISWAP_POOL_ABI,
isRecord,
coerceString,
getErrorMessage,
ensureAddress,
} from '@/composables/useSwapKrk';
import { SWAP_ROUTER_ABI, UNISWAP_FACTORY_ABI, UNISWAP_POOL_ABI } from '@/composables/useSwapKrk';
import { isRecord, coerceString, getErrorMessage, ensureAddress } from '@harb/utils';
const toast = useToast();
const statCollection = useStatCollection();