diff --git a/packages/utils/README.md b/packages/utils/README.md new file mode 100644 index 0000000..5cc7367 --- /dev/null +++ b/packages/utils/README.md @@ -0,0 +1,52 @@ +# @pnstack/utils + +A collection of utility functions for PNStack applications. + +## Installation + +```bash +pnpm add @pnstack/utils +``` + +## Usage + +```typescript +import { get, isEmpty, delay, randomString } from '@pnstack/utils'; + +// Access nested object properties safely +const obj = { user: { profile: { name: 'John' } } }; +const name = get(obj, 'user.profile.name'); // 'John' +const age = get(obj, 'user.profile.age', 0); // 0 (default value) + +// Check if value is empty +isEmpty(''); // true +isEmpty([]); // true +isEmpty({}); // true +isEmpty(null); // true +isEmpty(undefined); // true +isEmpty('hello'); // false + +// Delay execution +await delay(1000); // waits for 1 second + +// Generate random string +const id = randomString(10); // e.g. "a1b2c3d4e5" +``` + +## API Reference + +### get(obj: any, path: string, defaultValue?: T): T | undefined +Safely access nested object properties using dot notation or array syntax. + +### isEmpty(value: any): boolean +Check if a value is empty (null, undefined, empty string, empty array, empty object). + +### delay(ms: number): Promise +Creates a promise that resolves after the specified number of milliseconds. + +### randomString(length: number): string +Generates a random string of the specified length using alphanumeric characters. + +## License + +MIT \ No newline at end of file diff --git a/packages/utils/package.json b/packages/utils/package.json new file mode 100644 index 0000000..fc83ab7 --- /dev/null +++ b/packages/utils/package.json @@ -0,0 +1,58 @@ +{ + "name": "@pnstack/utils", + "version": "1.0.0", + "description": "Utility functions for PNStack applications", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/pnstack/template-turbo-ui.git", + "directory": "packages/utils" + }, + "publishConfig": { + "registry": "https://npm.pkg.github.com" + }, + "main": "dist/index.js", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.js" + } + }, + "engines": { + "node": ">=18" + }, + "scripts": { + "dev": "NODE_ENV=development BABEL_ENV=development rollup -c --watch", + "build": "rimraf dist && NODE_ENV=production BABEL_ENV=production rollup -c", + "lint": "pnpm check-types && pnpm eslint", + "lint:fix": "pnpm eslint:fix", + "eslint": "eslint . --report-unused-disable-directives --max-warnings 0", + "eslint:fix": "eslint . --fix", + "check-types": "tsc --noEmit true", + "test": "vitest --config ./vitest.config.ts", + "ci": "pnpm lint && pnpm test --watch=false", + "release": "pnpm ci && pnpm build && pnpm publish" + }, + "devDependencies": { + "@repo/eslint-config": "workspace:*", + "@repo/prettier-config": "workspace:*", + "@repo/typescript-config": "workspace:*", + "@rollup/plugin-commonjs": "^28.0.2", + "@rollup/plugin-node-resolve": "^16.0.0", + "@rollup/plugin-terser": "^0.4.4", + "@rollup/plugin-typescript": "^12.1.2", + "@types/node": "^22.10.2", + "rimraf": "^6.0.1", + "rollup": "^4.29.1", + "rollup-plugin-dts": "^6.1.1", + "rollup-plugin-peer-deps-external": "^2.2.4", + "tslib": "^2.8.1", + "typescript": "5.7.2", + "vitest": "^2.1.8" + }, + "packageManager": "pnpm@8.0.0" +} \ No newline at end of file diff --git a/packages/utils/rollup.config.mjs b/packages/utils/rollup.config.mjs new file mode 100644 index 0000000..aba1c86 --- /dev/null +++ b/packages/utils/rollup.config.mjs @@ -0,0 +1,30 @@ +import { defineConfig } from "rollup"; +import commonjs from "@rollup/plugin-commonjs"; +import resolve from "@rollup/plugin-node-resolve"; +import typescript from "@rollup/plugin-typescript"; +import terser from "@rollup/plugin-terser"; +import peerDepsExternal from "rollup-plugin-peer-deps-external"; + +export default defineConfig([ + { + input: "src/index.ts", + output: [ + { + file: "dist/index.js", + format: "esm", + sourcemap: true, + }, + ], + plugins: [ + peerDepsExternal(), + resolve(), + commonjs(), + typescript({ + tsconfig: "./tsconfig.json", + declaration: true, + declarationDir: "dist", + }), + terser(), + ], + }, +]); \ No newline at end of file diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts new file mode 100644 index 0000000..7b77308 --- /dev/null +++ b/packages/utils/src/index.ts @@ -0,0 +1,41 @@ +/** + * Safely access nested object properties + */ +export function get(obj: any, path: string, defaultValue?: T): T | undefined { + const travel = (regexp: RegExp) => + String.prototype.split + .call(path, regexp) + .filter(Boolean) + .reduce((res, key) => (res !== null && res !== undefined ? res[key] : res), obj); + const result = travel(/[,[\]]+?/) || travel(/[,[\].]+?/); + return result === undefined || result === obj ? defaultValue : result; +} + +/** + * Check if value is empty (null, undefined, empty string, empty array, empty object) + */ +export function isEmpty(value: any): boolean { + if (value === null || value === undefined) return true; + if (typeof value === "string") return value.trim().length === 0; + if (Array.isArray(value)) return value.length === 0; + if (typeof value === "object") return Object.keys(value).length === 0; + return false; +} + +/** + * Delay execution for specified milliseconds + */ +export const delay = (ms: number): Promise => + new Promise((resolve) => setTimeout(resolve, ms)); + +/** + * Generate a random string of specified length + */ +export function randomString(length: number): string { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let result = ""; + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; +} \ No newline at end of file diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json new file mode 100644 index 0000000..5f6dc8a --- /dev/null +++ b/packages/utils/tsconfig.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "composite": false, + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "inlineSources": false, + "isolatedModules": true, + "moduleResolution": "Bundler", + "noUnusedLocals": false, + "noUnusedParameters": false, + "preserveWatchOutput": true, + "skipLibCheck": true, + "strict": true, + "strictNullChecks": true, + "lib": ["ES2015", "DOM"], + "target": "ES2015", + "module": "ESNext", + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/packages/utils/vitest.config.ts b/packages/utils/vitest.config.ts new file mode 100644 index 0000000..c7da6b3 --- /dev/null +++ b/packages/utils/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + }, +}); \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c8a36f..5a6c761 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -221,6 +221,54 @@ importers: specifier: ^2.1.8 version: 2.1.8(@types/node@22.10.7)(jsdom@25.0.1)(sass@1.83.4)(terser@5.37.0) + packages/utils: + devDependencies: + '@repo/eslint-config': + specifier: workspace:* + version: link:../config-eslint + '@repo/prettier-config': + specifier: workspace:* + version: link:../config-prettier + '@repo/typescript-config': + specifier: workspace:* + version: link:../config-typescript + '@rollup/plugin-commonjs': + specifier: ^28.0.2 + version: 28.0.2(rollup@4.31.0) + '@rollup/plugin-node-resolve': + specifier: ^16.0.0 + version: 16.0.0(rollup@4.31.0) + '@rollup/plugin-terser': + specifier: ^0.4.4 + version: 0.4.4(rollup@4.31.0) + '@rollup/plugin-typescript': + specifier: ^12.1.2 + version: 12.1.2(rollup@4.31.0)(tslib@2.8.1)(typescript@5.7.2) + '@types/node': + specifier: ^22.10.2 + version: 22.10.7 + rimraf: + specifier: ^6.0.1 + version: 6.0.1 + rollup: + specifier: ^4.29.1 + version: 4.31.0 + rollup-plugin-dts: + specifier: ^6.1.1 + version: 6.1.1(rollup@4.31.0)(typescript@5.7.2) + rollup-plugin-peer-deps-external: + specifier: ^2.2.4 + version: 2.2.4(rollup@4.31.0) + tslib: + specifier: ^2.8.1 + version: 2.8.1 + typescript: + specifier: 5.7.2 + version: 5.7.2 + vitest: + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.10.7)(jsdom@25.0.1)(sass@1.83.4)(terser@5.37.0) + packages: '@adobe/css-tools@4.4.1':