Install
$ agentstack add skill-kinhluan-skills-javascript-typescript Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Pipes remote content directly into a shell (remote code execution).
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
JavaScript & TypeScript
Modern JS/TS development covering project setup, TypeScript strict mode, React/Next.js patterns, state management, testing, linting, and production deployment. Targets Node.js 20+, TypeScript 5.5+, React 19+, pnpm, bun.
> "Make invalid states unrepresentable." — The TypeScript Way
1. Project Setup
Package Manager
| Tool | Best For | Install | Speed | Lockfile | |---|---|---|---|---| | pnpm (recommended) | Monorepos, disk efficiency, strict dependency resolution | corepack enable && corepack prepare pnpm@latest --activate | Fast | pnpm-lock.yaml | | bun | Runtime + bundler + test runner in one, fastest install | curl -fsSL https://bun.sh/install \| bash | Fastest | bun.lockb | | npm | Default, no setup needed | Built-in with Node.js | Slowest | package-lock.json |
pnpm workflow:
# Enable corepack (Node.js 16.13+) — one-time setup
corepack enable
corepack prepare pnpm@latest --activate
# Project init
pnpm init
pnpm add typescript react react-dom
pnpm add -D @types/react @types/react-dom eslint prettier vitest
# Monorepo workspace
pnpm-workspace.yaml:
packages:
- 'packages/*'
- 'apps/*'
# Workspace commands
pnpm -r install # install all workspaces
pnpm --filter web build # build only 'web' package
pnpm -r exec tsc --noEmit # type-check all
bun workflow:
# Install (macOS/Linux)
curl -fsSL https://bun.sh/install | bash
# Project init — creates bun.lockb, package.json, tsconfig.json
bun init
# Install dependencies (10-100x faster than npm)
bun add typescript react react-dom
bun add -D @types/react @types/react-dom
# Run scripts (no need for 'run')
bun dev # runs 'dev' script
bun test # built-in test runner (Jest-compatible)
bun build # built-in bundler
# Runtime: drop-in Node.js replacement
bun run server.ts # instead of node server.ts
bun --hot server.ts # hot reload (no nodemon needed)
When to choose what:
Monorepo with 5+ packages → pnpm (workspace filtering, strict hoisting)
Single app, speed critical → bun (runtime + bundler + test in one)
Enterprise / CI consistency → pnpm (mature, predictable, corepack)
Quick prototype / side project → bun (fastest everything)
Lockfile hygiene (critical for CI reproducibility):
# pnpm — deterministic, content-addressable
pnpm install --frozen-lockfile # CI: fail if lockfile out of sync
pnpm install --no-frozen-lockfile # Local: update lockfile
# bun — binary lockfile, very fast
bun install --frozen-lockfile # CI
bun install --no-save # Local, don't update lockfile
TypeScript Configuration (tsconfig.json)
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"],
"@/lib/*": ["./src/lib/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Key strict flags: | Flag | Effect | |---|---| | strictNullChecks | null/undefined are separate types | | noUncheckedIndexedAccess | arr[0] is T \| undefined | | exactOptionalPropertyTypes | ? means absent, not \| undefined |
2. Type Best Practices
Prefer Interfaces for Object Shapes
// ✅ Interface — extensible, better error messages
interface User {
id: string;
name: string;
email: string;
}
// ✅ Type — for unions, tuples, mapped types
type Status = 'idle' | 'loading' | 'success' | 'error';
type ApiResponse = { data: T; status: number } | { error: string; status: number };
// ❌ Don't use type for simple objects (unless you need union)
Discriminated Unions
type LoadingState = { status: 'loading' };
type SuccessState = { status: 'success'; data: T };
type ErrorState = { status: 'error'; error: string };
type AsyncState = LoadingState | SuccessState | ErrorState;
function handleState(state: AsyncState): T | null {
switch (state.status) {
case 'loading': return null;
case 'success': return state.data; // TypeScript knows data exists
case 'error': throw new Error(state.error);
default: return exhaustiveCheck(state);
}
}
function exhaustiveCheck(x: never): never {
throw new Error(`Unhandled case: ${x}`);
}
Branded Types for Type-Safe IDs
type UserId = string & { __brand: 'UserId' };
type OrderId = string & { __brand: 'OrderId' };
function createUserId(id: string): UserId {
return id as UserId;
}
// Now this is a type error:
// const userId: UserId = createUserId('123');
// const orderId: OrderId = userId; // ❌ Compile error
Utility Types
// Partial, Required, Pick, Omit
type UpdateUserInput = Partial>;
// ReturnType, Parameters
type ApiReturn = ReturnType;
type ApiParams = Parameters;
// Record, Readonly
type ConfigMap = Record;
type ImmutableUser = Readonly;
// Awaited (unwraps Promise)
type UserData = Awaited>;
3. Modern JavaScript (ES2022+)
Nullish Coalescing & Optional Chaining
const value = config.timeout ?? 5000; // only null/undefined, not 0 or ''
const name = user?.profile?.name ?? 'Anonymous';
Top-Level Await
// In ES modules
const data = await fetch('/api/config').then(r => r.json());
export const config = data;
Private Class Fields
class Counter {
#count = 0; // truly private, not just convention
increment() {
this.#count++;
return this.#count;
}
get #formatted() { // private getter
return `Count: ${this.#count}`;
}
}
Array Methods
const nums = [1, 2, 3, 4, 5];
// Modern methods (no mutation)
const doubled = nums.map(n => n * 2);
const evens = nums.filter(n => n % 2 === 0);
const sum = nums.reduce((a, b) => a + b, 0);
const firstEven = nums.find(n => n % 2 === 0); // number | undefined
const hasEven = nums.some(n => n % 2 === 0);
const allPositive = nums.every(n => n > 0);
// toSorted, toReversed, toSpliced (ES2023 — non-mutating)
const sorted = nums.toSorted((a, b) => b - a);
const reversed = nums.toReversed();
4. React 19+ Patterns
Server Components (Next.js App Router)
// app/page.tsx — Server Component by default
import { db } from '@/lib/db';
export default async function Page() {
const users = await db.query('SELECT * FROM users'); // runs on server
return (
Users
{/* Client Component */}
);
}
// components/UserForm.tsx — Client Component
'use client';
import { useState } from 'react';
export function UserForm() {
const [name, setName] = useState('');
// ... client-side interactivity
}
Hooks Best Practices
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
// Custom hook for data fetching
function useApi(url: string) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(r => r.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
return () => controller.abort();
}, [url]);
return { data, error, loading };
}
// Usage
function UserProfile({ userId }: { userId: string }) {
const { data: user, error, loading } = useApi(`/api/users/${userId}`);
if (loading) return ;
if (error) return ;
if (!user) return ;
return ;
}
useMemo / useCallback
// ✅ Use when computation is expensive
const sortedUsers = useMemo(
() => users.sort((a, b) => a.name.localeCompare(b.name)),
[users]
);
// ✅ Use when passing callbacks to optimized children
const handleSubmit = useCallback(
(data: FormData) => {
api.submit(data);
},
[]
);
// ❌ Don't overuse — React is fast enough for simple cases
React 19 Actions
// Server Actions (Next.js)
'use server';
export async function createUser(formData: FormData) {
'use server';
const name = formData.get('name') as string;
await db.insert('users', { name });
revalidatePath('/users');
}
// Client usage with useActionState
import { useActionState } from 'react';
function UserForm() {
const [state, formAction, pending] = useActionState(createUser, null);
return (
{pending ? 'Creating...' : 'Create'}
{state?.error && {state.error}}
);
}
5. State Management
Zustand (Recommended)
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
interface UserStore {
user: User | null;
setUser: (user: User | null) => void;
logout: () => void;
}
export const useUserStore = create()(
devtools(
persist(
(set) => ({
user: null,
setUser: (user) => set({ user }),
logout: () => set({ user: null }),
}),
{ name: 'user-storage' }
)
)
);
// Usage
function Profile() {
const { user, logout } = useUserStore();
// ...
}
TanStack Query (Server State)
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// Fetch
function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json()),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
// Mutate
function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (user: NewUser) =>
fetch('/api/users', { method: 'POST', body: JSON.stringify(user) }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
}
6. Testing
Vitest (Recommended over Jest)
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
},
});
// src/utils/calculate.test.ts
import { describe, it, expect } from 'vitest';
import { calculateTotal } from './calculate';
describe('calculateTotal', () => {
it('sums items correctly', () => {
const items = [
{ price: 10, quantity: 2 },
{ price: 5, quantity: 1 },
];
expect(calculateTotal(items)).toBe(25);
});
it('handles empty cart', () => {
expect(calculateTotal([])).toBe(0);
});
it('throws on negative price', () => {
expect(() => calculateTotal([{ price: -1, quantity: 1 }]))
.toThrow('Price must be positive');
});
});
React Testing Library
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { UserForm } from './UserForm';
it('submits form with user data', async () => {
const onSubmit = vi.fn();
render();
fireEvent.change(screen.getByLabelText(/name/i), {
target: { value: 'Alice' },
});
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({ name: 'Alice' });
});
});
Playwright (E2E)
// tests/e2e/login.spec.ts
import { test, expect } from '@playwright/test';
test('user can log in', async ({ page }) => {
await page.goto('/login');
await page.fill('[name="email"]', 'user@example.com');
await page.fill('[name="password"]', 'password');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
});
7. Linting & Formatting
ESLint (Flat Config)
// eslint.config.js
import js from '@eslint/js';
import ts from 'typescript-eslint';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import prettier from 'eslint-config-prettier';
export default [
js.configs.recommended,
...ts.configs.recommendedTypeChecked,
react.configs.flat.recommended,
reactHooks.configs['recommended-latest'],
prettier,
{
languageOptions: {
parserOptions: {
project: './tsconfig.json',
},
},
rules: {
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/explicit-function-return-type': 'off',
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
},
},
];
Prettier
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100
}
8. Node.js Backend
Fastify (Recommended over Express)
import fastify from 'fastify';
import { z } from 'zod';
const app = fastify({ logger: true });
// Validation with Zod
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().min(0).optional(),
});
app.post('/users', async (request, reply) => {
const body = createUserSchema.parse(request.body);
const user = await db.users.create(body);
reply.status(201).send(user);
});
// Error handler
app.setErrorHandler((error, request, reply) => {
app.log.error(error);
if (error instanceof z.ZodError) {
reply.status(400).send({ error: 'Validation failed', details: error.errors });
return;
}
reply.status(500).send({ error: 'Internal server error' });
});
await app.listen({ port: 3000 });
tRPC (Type-Safe APIs)
// server/router.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
export const appRouter = t.router({
user: t.router({
get: t.procedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return db.users.findById(input.id);
}),
create: t.procedure
.input(z.object({ name: z.string(), email: z.string().email() }))
.mutation(async ({ input }) => {
return db.users.create(input);
}),
}),
});
export type AppRouter = typeof appRouter;
// client usage
import { createTRPCReact } from '@trpc/react-query';
const trpc = createTRPCReact();
// Fully type-safe: autocomplete works for 'user.get', 'user.create'
const { data } = trpc.user.get.useQuery({ id: '123' });
9. Integration with Other Skills
| This skill provides | Related skill | For deeper dive | |---|---|---| | React/Next.js patterns | python-development | FastAPI backend for full-stack | | Docker for JS apps | docker-containerization | Multi-stage builds, Node.js images | | K8s deployment | kubernetes-orchestration | Manifests for JS services | | Security | security-analysis | OWASP for web apps, auth patterns | | Testing | evolutionary-architecture | Fitness functions, architecture tests |
10. Bun Runtime Deep Dive
Bun is an all-in-one JavaScript runtime (faster than Node.js), bundler, test runner, and package manager.
Bun vs Node.js
| Feature | Bun | Node.js | |---|---|---| | Runtime speed | ~3x faster | Baseline | | Package manager | Built-in (bun install) | npm (external) | | Bundler | Built-in (bun build) | webpack/esbuild/rollup (external) | | Test runner | Built-in (bun test) | jest/vitest (external) | | TypeScript | Native (no ts-node) | Requires transpilation | | ESM/CJS | Both, seamless | ESM still experimental in some cases | | .env loading | Built-in (`Bun.env
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: kinhluan
- Source: kinhluan/skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.