
Js Skills
- 18 installs
- 1 repo stars
- Updated July 23, 2026
- mym0404/agent-skills
Helps with ai & agent building tasks.
About
js-skills is a Claude Code skill for ai & agent building. It helps developers move faster with AI-assisted coding.
- js-skills
- AI & Agent Building
- AI-coding skill
Js Skills by the numbers
- 18 all-time installs (skills.sh)
- Ranked #10,736 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 24, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mym0404/agent-skills --skill js-skillsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 18 |
|---|---|
| repo stars | ★ 1 |
| Last updated | July 23, 2026 |
| Repository | mym0404/agent-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
JS Skills — Schema, Dayjs, Util, Module & Structure Patterns
Consistent Zod schema definition rules, dayjs date/time conventions, guide-driven utility refactoring rules, class-based module patterns, Zustand store patterns, and feature-first architecture patterns extracted from production domain modeling.
When to Apply
Reference these guidelines when:
- Defining new domain model schemas with Zod
- Extending base schemas for entity models
- Integrating const assertion enums with Zod validation
- Composing or deriving schemas from existing ones
- Creating model instances with auto-generated base fields
- Setting up dayjs plugins and locale in a project
- Working with timestamps, time arithmetic, formatting, or durations
- Using or reviewing public APIs from
@mj-studio/js-util - Building a stateful module with internal mutable state and related methods
- Creating standard, persisted, or provider-based Zustand stores
- Organizing code by feature folders with
feature/{domain}andfeature/commonfor shared pieces
Rule Categories by Priority
| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | Schema Patterns | HIGH | schema- |
| 2 | Dayjs Patterns | HIGH | dayjs- |
| 3 | JS Util Patterns | HIGH | js-util- |
| 4 | Module Patterns | HIGH | module- |
| 5 | Zustand Patterns | HIGH | zustand- |
| 6 | Architecture Patterns | HIGH | architecture- |
Quick Reference
1. Schema Patterns (HIGH)
schema-definition-patterns— Current Zod 4 definition helpers, logical schema grouping, type inference, composition, and createModel flow
2. Dayjs Patterns (HIGH)
dayjs-usage— Plugin setup, timestamp ops, time arithmetic, formatting, duration
3. JS Util Patterns (HIGH)
js-util-usage— Use upstreamllms.txtto discover, apply, and refactor toward@mj-studio/js-utilhelpers instead of bespoke utility code
4. Module Patterns (HIGH)
module-class-over-factory— Use a class instead of a closure factory object when a module owns mutable state and related methods
5. Zustand Patterns (HIGH)
zustand-store-patterns— Use consistent patterns for normal stores, persisted stores, and provider-backed stores
6. Architecture Patterns (HIGH)
architecture-broad-domain-nesting— Organize code by feature, default tofeature/{domain}, allow only one nestedfeature/{parent}/feature/{child}split when unavoidable, keep shared pieces infeature/common
How to Use
Read individual rule files for details and examples:
rules/schema-definition-patterns.md
rules/dayjs-usage.md
rules/js-util-usage.md
rules/module-class-over-factory.md
rules/zustand-store-patterns.md
rules/architecture-broad-domain-nesting.mdEach rule file contains:
- Why the rule matters
- Incorrect implementation example
- Correct implementation example
- Reference links
{
"version": "2.9.4",
"organization": "Engineering",
"date": "March 2026",
"abstract": "Zod schema definition patterns, dayjs date/time handling conventions, guide-driven refactoring rules for @mj-studio/js-util, class-based stateful module patterns, Zustand store patterns, and feature-first folder architecture rules with feature-specific code under feature folders.",
"references": [
"https://zod.dev",
"https://github.com/colinhacks/zod",
"https://day.js.org",
"https://github.com/mj-studio-library/js-util",
"https://github.com/mj-studio-library/js-util/blob/master/llms.txt",
"https://www.typescriptlang.org/docs/handbook/2/classes.html",
"https://martinfowler.com/bliki/BoundedContext.html",
"https://zustand.docs.pmnd.rs/getting-started/introduction"
]
}
Sections
This file defines section ordering, impact levels, and filename prefixes.
---
1. Schema Patterns (schema)
Impact: HIGH Description: Zod schema definition, composition, and instance creation patterns for domain modeling.
2. Dayjs Patterns (dayjs)
Impact: HIGH Description: Dayjs plugin setup, timestamp operations, time arithmetic, formatting, and duration patterns.
3. JS Util Patterns (js-util)
Impact: HIGH Description: Guide-driven rules for adopting @mj-studio/js-util helpers to simplify and refactor project code.
4. Module Patterns (module)
Impact: HIGH Description: Prefer class-based modules over closure factories when a module owns mutable state and related methods.
5. Zustand Patterns (zustand)
Impact: HIGH Description: Use consistent patterns for standard stores, persisted stores, and provider-backed stores with Zustand.
6. Architecture Patterns (architecture)
Impact: HIGH Description: Organize code by feature, default to feature/{domain}, allow only one nested feature/{parent}/feature/{child} split when unavoidable, and keep shared code in feature/common.
Rule Title Here
Brief explanation of why this rule matters.
Incorrect:
// Bad exampleCorrect:
// Good exampleReference: Link
Organize Code by Feature and Keep Feature-Specific Code Inside feature/{domain}
When a codebase is organized by folders, prefer feature-first structure. Top-level feature folders should represent real product/application features, feature-specific code such as model, types, and ui should live under feature/{domain}, shared cross-feature code should live under feature/common, and submodules should stay inside the feature that owns them instead of becoming new sibling roots.
Default to feature/{domain}. Only when a child area is genuinely large enough and tightly coupled to its parent feature, allow one nested split as feature/{parent}/feature/{child}/.... Do not let nested feature depth exceed 2.
Incorrect:
src/model/auth/User.ts
src/types/auth/Role.ts
src/ui/auth/LoginPage.tsx
src/feature/campaign-logs/
src/feature/campaign-results/
src/feature/hooks/
src/feature/utils/
src/feature/components/
src/feature/campaigns/feature/results/feature/export/Why it fails: Related code gets split by technical type instead of ownership. Shared and feature-specific pieces are mixed together, feature-owned folders escape their feature boundary, parent-owned areas such as campaign-logs or campaign-results drift into fake top-level features, and nested feature depth grows beyond what remains easy to scan.
Correct:
src/
feature/
common/
hooks/
providers/
utils/
ui/
components/
auth/
model/
types/
ui/
pages/
campaigns/
model/
types/
hooks/
providers/
repositories/
logic/
ui/
components/
pages/
feature/
logs/
results/
accounts/
model/
types/
hooks/
providers/
repositories/
logic/
ui/
pages/Feature folders may contain only the subfolders they actually need. Common examples are:
model/types/utils/hooks/providers/repositories/logic/ui/components/ui/pages/
Apply this rule as follows:
- Split primary application code by feature ownership first.
- Put feature-specific folders such as
model,types, and feature-localuiinsidefeature/{domain}/. - Put cross-feature reusable code in
feature/common/. - Keep parent-owned submodules such as
logs,results,forms, ortemplatesunder the owning feature. - Only when separation is unavoidable, split a child area as
feature/{parent}/feature/{child}/. - Do not nest
feature/.../feature/.../feature/...; maximum nested feature depth is 2. - Do not create new top-level feature folders just because a nested area has multiple files.
Use this pattern when:
- a module belongs clearly to one feature
- some helpers are reused widely enough to justify
feature/common/ - a nested area shares lifecycle, ownership, or vocabulary with its parent feature
Reference: Bounded Context
Dayjs Usage Patterns
Use dayjs as the sole date/time library. Never mix with raw Date or Date.now(). Register plugins and locales in a single boot entry point. If the project already defines a standard dayjs plugin set, register all listed plugins there unless a concrete, documented runtime constraint requires leaving one out.
Plugin Setup
All plugin imports and registrations go in one boot/init file. Name plugin imports as dayjs + PascalCase plugin name + Plugin. Prefer the full project-approved plugin set over partial registration.
Incorrect:
// Partial registration in the boot file
import dayjs from 'dayjs';
import dayjsDurationPlugin from 'dayjs/plugin/duration';
dayjs.extend(dayjsDurationPlugin);Why it fails: A centralized boot file exists, but it does not register the full plugin set the project expects. Features that rely on omitted plugins start failing later, and dayjs behavior becomes inconsistent across modules.
Correct:
// src/bootlogic/bootlogic.ts (single entry point)
import 'dayjs/locale/ko';
import 'dayjs/locale/en';
import dayjs from 'dayjs';
import dayjsArraySupportPlugin from 'dayjs/plugin/arraySupport';
import dayjsDurationPlugin from 'dayjs/plugin/duration';
import dayjsIsoWeekPlugin from 'dayjs/plugin/isoWeek';
import dayjsRelativeTimePlugin from 'dayjs/plugin/relativeTime';
import dayjsIsoTimeZonePlugin from 'dayjs/plugin/timezone';
import dayjsIsoUTCPlugin from 'dayjs/plugin/utc';
// Register the full project-approved plugin set in one place.
dayjs.extend(dayjsDurationPlugin);
dayjs.extend(dayjsArraySupportPlugin);
dayjs.extend(dayjsIsoWeekPlugin);
dayjs.extend(dayjsIsoUTCPlugin);
dayjs.extend(dayjsIsoTimeZonePlugin);
dayjs.extend(dayjsRelativeTimePlugin);
dayjs.locale(currentLocale);Plugin import naming:
| Plugin | Import Alias |
|---|---|
dayjs/plugin/duration | dayjsDurationPlugin |
dayjs/plugin/utc | dayjsIsoUTCPlugin |
dayjs/plugin/timezone | dayjsIsoTimeZonePlugin |
dayjs/plugin/isoWeek | dayjsIsoWeekPlugin |
dayjs/plugin/relativeTime | dayjsRelativeTimePlugin |
dayjs/plugin/arraySupport | dayjsArraySupportPlugin |
If the codebase documents a dayjs plugin list, treat that list as the default boot set and register all of it in the central dayjs boot file.
Timestamp Operations
Use dayjs().valueOf() for unix timestamps in milliseconds. Never use Date.now() or new Date().getTime().
Incorrect:
const now = Date.now();
const futureMs = now + 60 * 1000;
const elapsed = Date.now() - startTime;Correct:
const now = dayjs().valueOf();
const futureMs = dayjs().valueOf() + Const.restoreSeconds * 1000;
const elapsed = dayjs().valueOf() - startUnixMs;Time Arithmetic
Use dayjs methods (.add(), .diff(), .isBefore()) instead of raw ms arithmetic when computing relative time.
Incorrect:
const oneYearLater = Date.now() + 365 * 24 * 60 * 60 * 1000;
const diffSec = (targetMs - Date.now()) / 1000;
const isExpired = targetMs < Date.now();Correct:
const oneYearLater = dayjs().add(1, 'year').valueOf();
const diffSec = dayjs(targetMs).diff(dayjs(), 'second');
const isExpired = dayjs(targetMs).isBefore(dayjs());Formatting
Use .format() with explicit format strings.
dayjs(timestamp).format('YYYY/MM/DD HH:mm');Duration
Use dayjs.duration() for representing time spans. Wrap in utility functions for common units.
export const durationSec = (sec: number) => dayjs.duration(sec, 'second');
export const durationMs = (ms: number) => dayjs.duration(ms, 'millisecond');Reference: dayjs documentation dayjs plugins
@mj-studio/js-util Guide-Driven Refactoring
When string, object, array, or JSON-like utility code is being added, reviewed, or refactored, check whether @mj-studio/js-util already documents a public helper that can replace custom logic. The point of this rule is not to restate the guide locally, but to make the guide actively shape implementation and refactoring decisions.
Guide reference:
Required Workflow
1. Open the upstream llms.txt before adding or refactoring utility logic in the domains covered by @mj-studio/js-util. 2. If the user explicitly requests using @mj-studio/js-util and the package is not installed in the project, install it first. 3. Check whether an existing public helper can replace the local code with a simpler and clearer implementation. 4. If the documented helper matches the intended semantics, prefer refactoring to that helper instead of keeping bespoke utility code. 5. Use the guide to discover supported patterns and best practices, then apply them directly in the project code. 6. Re-check llms.txt whenever function behavior, options, or naming is uncertain.
Do Not
- Do not treat
@mj-studio/js-utilas a passive reference only. Use it to simplify code when it is a good semantic match. - Do not install the package proactively when the user did not ask to use
@mj-studio/js-util. - Do not keep ad-hoc utility implementations if the package already provides the same behavior clearly.
- Do not wrap or recreate documented helpers without a concrete project-specific reason.
- Do not paraphrase all function docs into this skill. Keep this rule focused on when and how to apply the upstream guide.
This rule applies to every public function from @mj-studio/js-util. The upstream llms.txt should be used as the working guide for discovering replacement opportunities, simplifying existing code, and standardizing utility usage across the project.
Reference: Repository @mj-studio/js-util llms.txt
Prefer Classes over Closure Factories for Stateful Modules
When a module owns internal mutable state and exposes multiple related methods, prefer a class over a createX() closure factory that returns an object. Use the class instance as the module boundary instead of hiding state inside a factory function. Within that class, default to private for internal members, add readonly when a field should not be reassigned, and prefer constructor parameter properties for stored constructor inputs.
Incorrect:
const createWriteDebugLogger = () => {
const logs: DcinsideDebugLog[] = [];
const append = ({
step,
level,
message,
detail,
}: {
step: string;
level: DcinsideDebugLogLevel;
message: string;
detail?: Record<string, unknown>;
}) => {
const nextLog: DcinsideDebugLog = {
id: `dcinside-write-log-${logs.length + 1}`,
time: dayjs().toISOString(),
step,
level,
message,
detail: detail ? JSON.stringify(detail, null, 2) : null,
};
logs.push(nextLog);
console.info('[dcinside-write]', nextLog.time, nextLog.step, nextLog.detail ?? '');
};
return {
append,
getLogs: () => [...logs],
};
};Why it fails: The module has clear state and related behavior, but the factory hides that structure inside a function closure. The resulting object is less explicit than a named class, harder to extend, and less discoverable when the module grows.
Correct:
export class WriteDebugLogger {
private readonly logs: DcinsideDebugLog[] = [];
constructor(private readonly logPrefix = 'dcinside-write') {}
append({
step,
level,
message,
detail,
}: {
step: string;
level: DcinsideDebugLogLevel;
message: string;
detail?: Record<string, unknown>;
}) {
const nextLog: DcinsideDebugLog = {
id: `${this.logPrefix}-log-${this.logs.length + 1}`,
time: dayjs().toISOString(),
step,
level,
message,
detail: detail ? JSON.stringify(detail, null, 2) : null,
};
this.logs.push(nextLog);
console.info(`[${this.logPrefix}]`, nextLog.time, nextLog.step, nextLog.detail ?? '');
}
getLogs() {
return [...this.logs];
}
}
const logger = new WriteDebugLogger();Use this pattern when:
- the module keeps mutable state across method calls
- multiple methods operate on the same state
- the module benefits from a named, explicit public surface
Class authoring details:
- Use
privatefor fields and helper methods that are not part of the public surface. - Add
readonlywhen a field or dependency should not be reassigned after initialization. - Prefer constructor parameter properties such as
constructor(private readonly logPrefix: string) {}over separate field declarations plus manual assignments when the constructor input is stored directly.
Reference: TypeScript Classes MDN Classes
Schema Definition Patterns
Use one cohesive schema flow: verify current Zod APIs with Context7, group related schemas and helpers by domain ownership, infer types from schemas, reuse meaningful fragments, and create instances through schema-validated model factories.
Naming, Grouping, and Type Inference
Follow consistent naming for schema files, schema constants, and inferred types. The primary rule is logical grouping, not forced file splitting. Never define a duplicate TypeScript type beside the schema.
Incorrect:
// File: misc.ts
export const schema = z.object({ ... });
export const sourceSchema = z.object({ ... });
export const resolvedSchema = z.object({ ... });
export type ProfileType = {
...
};Why it fails: The file name hides ownership, schema names are too generic to search, and the duplicated type can drift from the schema.
Correct:
// File: ProductProfile.ts
export const productProfileSourceSchema = z.object({
...
});
export const productProfileResolvedSchema = z.object({
...
});
export type ProductProfileSource = z.infer<typeof productProfileSourceSchema>;
export type ProductProfileResolved = z.infer<typeof productProfileResolvedSchema>;Rules:
- Prefer file names that reflect the domain concept or cohesive schema family, such as
ProductProfile.ts. - Schema constants should be specific and searchable, such as
productProfileSourceSchema. - Inferred types should come from schemas with
z.infer. - It is fine to export multiple related schemas from one file when they describe the same concept, such as source, resolved, derived, or request/response variants.
- If a schema or schema family is broadly reused across the codebase, it is also fine to split it into its own dedicated file for shared ownership and discoverability.
- Do not split files just to satisfy a one-schema-per-file rule.
- Tightly coupled parser helpers, labels, or UI maps may stay in the same module when they clearly belong to that concept and are not broadly shared.
Latest Zod Definition API
Check Context7 before introducing or changing schema definition patterns. Prefer current Zod 4 helpers over deprecated chains. Keep simple format validators inline, and avoid extra strictness or transformation unless there is a concrete business requirement.
Incorrect:
const recordIdSchema = z.string().uuid();
const createdAtSchema = z.string().datetime();
const callbackUrlSchema = z.string().url();
export const webhookSchema = z
.object({
id: recordIdSchema,
createdAt: createdAtSchema,
callbackUrl: callbackUrlSchema,
tags: z.array(z.string()).min(1),
})
.strict()
.transform(value => ({
...value,
createdAt: dayjs(value.createdAt),
}));Why it fails: It uses deprecated format validators, extracts trivial single-use validators, adds strictness and length constraints without domain justification, and converts serialized datetime values at the schema boundary.
Correct:
const webhookEventSchema = z.object({
type: z.string(),
payload: z.string(),
});
export const webhookSchema = z.object({
id: z.uuid(),
createdAt: z.iso.datetime(),
callbackUrl: z.url(),
tags: z.array(z.string()).optional(),
events: z.array(webhookEventSchema).optional(),
});Rules:
- Prefer
z.url(),z.uuid(), andz.iso.datetime()over deprecated chains such asz.string().url(),z.string().uuid(), andz.string().datetime(). - Keep simple field-level format validators inline. Do not extract single-use schemas such as
const urlSchema = z.url(). - Extract repeated, meaningful schema fragments and reuse them.
- Do not default to
z.strictObject()or.strict(). Use strict object validation only when unknown keys are a real business error. - Do not add constraints such as
.min(1)unless the domain explicitly requires them. - Use
preprocess,transform,pipe, andcoerceonly when the input source genuinely requires normalization or coercion. - Validate serialized datetimes with
z.iso.datetime(). Do not model schema boundary values asDate,Dayjs, or codec-driven wrappers.
Reusable Enum-Like Values
When literal values must be shared between runtime code and a schema, define them once as a const assertion array and derive both the Zod schema and the TypeScript type from that source.
Incorrect:
type Status = 'pending' | 'active' | 'inactive';
const statusSchema = z.enum(['pending', 'active', 'inactive']);Why it fails: The literal values are duplicated and can drift when one side changes first.
Correct:
export const allStatuses = ['pending', 'active', 'inactive'] as const;
export type Status = (typeof allStatuses)[number];
export const recordSchema = z.object({
status: z.enum(allStatuses),
});Composition and Reuse
Compose schemas through nesting, extending, picking, and partial derivation. Do not re-list the same fields across related schemas.
Incorrect:
export const personalRecordOmrSchema = z.object({
name: z.string().max(10),
xrayCode: z.int(),
jumin: z.string().regex(/^\d{6}-\d{7}$/),
highSchool: z.int().optional(),
});Why it fails: Re-listing fields duplicates validation logic and makes schema drift likely.
Correct:
export const personalRecordOmrSchema = personalRecordSchema
.pick({
name: true,
xrayCode: true,
jumin: true,
highSchool: true,
})
.partial();const detailedScoreSchema = z.object({
aptitudeScore: z.number().optional(),
licenseScore: z.number().optional(),
totalScore: z.number().optional(),
});
export const divisionPreferenceSchema = baseModelSchema.extend({
basicRecordId: z.uuid(),
firstPreferenceScores: detailedScoreSchema.optional(),
secondPreferenceScores: detailedScoreSchema.optional(),
});Related derived schemas can live beside the base schema when that grouping makes the domain easier to understand. Prefer splitting only when a schema family grows independent responsibilities or is reused by clearly separate modules.
Model Creation
Instantiate domain entities through createModel so schema validation and base field generation happen in one path.
Incorrect:
const record = {
id: crypto.randomUUID(),
createdAt: new Date().toISOString(),
sequence: 1,
name: 'John',
sn: '12345678',
};Why it fails: Base field creation is duplicated and the instance bypasses schema validation.
Correct:
const record = createModel(basicRecordSchema, {
sequence: 1,
name: 'John',
sn: '12345678',
xrayCode: 100,
division: 'engineering',
jumin: '900101-1234567',
});export const createModel = <T extends ZodType>(
schema: T,
value: Omit<z.infer<T>, keyof BaseModel>,
) => {
return schema.parse(value);
};Reference: Zod 4 Zod API Zod Type Inference TypeScript const assertions
Use Consistent Zustand Store Patterns
Use Zustand with three explicit patterns depending on ownership and lifecycle:
- plain store for app-global in-memory state
- persisted store for state that must survive reloads
- provider-backed store for tree-scoped state instances
Keep state and actions in a single store type, define initialState from non-function fields, and prefer immer middleware for updates.
Plain Store
Use create() for the default global store shape. Expose the main hook as useX, keep a reset action in the store, and provide a shallow-selected useXStore convenience hook for full-store reads.
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
export type AuthState = {
reset: () => void;
};
const initialState: OmitFunctions<AuthState> = {};
export const useAuth = create<AuthState>()(
immer((set, get) => ({
...initialState,
reset: () => set(initialState),
})),
);
export const useAuthStore = () => useAuth(useShallow((state) => state));Persist Store
Use persist() only when the state must survive reloads. Persist stores should track hydration explicitly with _hasHydrated and _markHydrate, preserve the hydration flag on reset, and use createJSONStorage(() => zustandPersistStorage) as the storage boundary.
import { create } from 'zustand';
import { createJSONStorage, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
export type AuthState = {
_hasHydrated: boolean;
_markHydrate: () => void;
reset: () => void;
};
const initialState: OmitFunctions<AuthState> = {
_hasHydrated: false,
};
export const useAuth = create<AuthState>()(
persist(
immer((set, get) => ({
...initialState,
reset: () =>
set((state) => ({
...initialState,
_hasHydrated: state._hasHydrated,
})),
_markHydrate: () => set({ _hasHydrated: true }),
})),
{
version: 0,
name: 'Auth-storage',
storage: createJSONStorage(() => zustandPersistStorage),
onRehydrateStorage: (state) => () => state._markHydrate(),
},
),
);
export const useAuthStore = () => useAuth(useShallow((state) => state));Do not implement zustandPersistStorage in this rule. Treat it as a project-owned storage boundary.
Provider-Backed Store
Use a provider-backed store when each subtree needs its own isolated store instance. Standardize this through a reusable createZustandStoreProvider helper instead of hand-writing context wrappers per store.
export type AuthState = {};
export type AuthAction = {};
const initialState: AuthState = {};
export const {
Provider: AuthProvider,
useStoreProvider: useAuth,
useStoreProviderShallow: useAuthShallow,
} = createZustandStoreProvider<AuthState & AuthAction>({
name: 'Auth',
creator: immer((set, get) => ({
...initialState,
})),
});The helper should:
- create the store with
createStore - hold the store instance in context
- create the instance once with
useRefValue - expose
useStoreProvideranduseStoreProviderShallow - support optional equality functions through
useStoreWithEqualityFn
Store Utility Rules
- Define
initialStatewithOmitFunctions<T>so state defaults exclude actions. - If the project does not already have
OmitFunctions, add it in a shared utility location. - Keep
resetinside the store instead of duplicating reset logic at call sites. - Use
useShallowfor full-store selector convenience hooks such asuseAuthStore. - Keep store names consistent:
useAuthfor the main Zustand hook,useAuthStorefor the shallow all-state reader, andAuthProvider/useAuth/useAuthShallowfor provider-based variants.
OmitFunctions utility:
type NonFunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
export type OmitFunctions<T> = Pick<T, NonFunctionPropertyNames<T>>;Reference: Zustand Introduction Zustand Persist Middleware