Skip to content

Commit a35ddb7

Browse files
committed
refactor(toolkit-type-helpers): ♻️ enhance isPlainObject type guard with TanStack Query's implementation
The isPlainObject type guard has been improved with a more robust implementation from TanStack Query: - feat: ✨ add hasObjectPrototype helper to check for [object Object] toString tag - refactor: 🔨 improve type safety with UnknownObject and UnknownObjectWithAnyKey types - docs: 📝 add reference to TanStack Query source implementation
1 parent bcb4a78 commit a35ddb7

21 files changed

+544
-104
lines changed

.github/workflows/autofix.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- name: Setup pnpm
2222
uses: pnpm/action-setup@v4
2323
with:
24-
version: 9.15.4
24+
version: 9.15.5
2525

2626
- name: Setup Node.js
2727
uses: actions/setup-node@v4

.github/workflows/lint-and-type.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- name: Setup pnpm
2222
uses: pnpm/action-setup@v4
2323
with:
24-
version: 9.15.4
24+
version: 9.15.5
2525

2626
- name: Setup Node.js
2727
uses: actions/setup-node@v4
@@ -36,5 +36,5 @@ jobs:
3636
3737
- name: Run linters
3838
run: |
39-
pnpm lint:check-types
39+
pnpm lint:type-check
4040
pnpm lint:eslint

.github/workflows/release-and-publish(pkg.pr.new).yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- name: Setup pnpm
2323
uses: pnpm/action-setup@v4
2424
with:
25-
version: 9.15.4
25+
version: 9.15.5
2626

2727
- name: Setup Node.js 20.x
2828
uses: actions/setup-node@v4
@@ -36,4 +36,4 @@ jobs:
3636
pnpm build
3737
3838
- name: Deploy and test release via pkg.pr.new
39-
run: pnpm test:release
39+
run: pnpm release:test

.github/workflows/release-notes-and-publish(changelogithub).yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
- name: Setup pnpm
2929
uses: pnpm/action-setup@v4
3030
with:
31-
version: 9.15.4
31+
version: 9.15.5
3232

3333
- name: Setup Node.js
3434
uses: actions/setup-node@v4

.github/workflows/size-limit.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- name: Setup pnpm
2222
uses: pnpm/action-setup@v4
2323
with:
24-
version: 9.15.4
24+
version: 9.15.5
2525

2626
- name: Setup Node.js
2727
uses: actions/setup-node@v4

lint-staged.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export default {
22
"*.{js,ts,jsx,tsx}": () => "pnpm lint:eslint",
3-
"*.{ts,tsx}": () => "pnpm lint:check-types",
3+
"*.{ts,tsx}": () => "pnpm lint:type-check",
44
"package.json": ["pnpm lint:publint"],
55
"packages/**/*.{js,ts,jsx,tsx}": () => "pnpm lint:size",
66
};

package.json

+12-10
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,26 @@
22
"name": "@zayne-labs/toolkit-root",
33
"type": "module",
44
"version": "0.0.0",
5-
"packageManager": "[email protected].4",
5+
"packageManager": "[email protected].5",
66
"author": "Ryan Zayne",
77
"scripts": {
8-
"build": "pnpm --filter \"./packages/*\" build",
9-
"build:dev": "pnpm --filter \"./packages/*\" build:dev",
10-
"build:test": "pnpm --filter \"./packages/*\" build:test",
8+
"build": "pnpm --filter ./packages/* build",
9+
"build:dev": "pnpm --filter ./packages/* build:dev",
10+
"build:test": "pnpm --filter ./packages/* build:test",
1111
"bump": "bumpp",
1212
"inspect:eslint-config": "pnpx @eslint/config-inspector@latest",
13-
"lint:attw": "pnpm --filter \"./packages/*\" lint:attw",
14-
"lint:check-types": "pnpm --filter \"./packages/*\" lint:check-types",
13+
"lint:attw": "pnpm --filter ./packages/* lint:attw",
1514
"lint:eslint": "eslint . --max-warnings 0 --report-unused-disable-directives",
1615
"lint:eslint:interactive": "pnpx eslint-interactive@latest . --fix --max-warnings 0 --report-unused-disable-directives",
1716
"lint:format": "prettier --write .",
18-
"lint:publint": "pnpm --filter \"./packages/*\" version-package:publint",
19-
"lint:size": "pnpm --filter \"./packages/*\" lint:size",
17+
"lint:publint": "pnpm --filter ./packages/* version-package:publint",
18+
"lint:size": "pnpm --filter ./packages/* lint:size",
19+
"lint:type-check": "pnpm --filter ./packages/* lint:type-check",
2020
"prepare": "husky",
21-
"release": "pnpm --filter \"./packages/*\" release",
22-
"test:release": "pnpx pkg-pr-new publish './packages/*' --compact",
21+
"release": "pnpm --filter ./packages/* release",
22+
"release:test": "pnpx pkg-pr-new publish ./packages/* --compact",
23+
"test": "pnpm --filter ./packages/* test",
24+
"test:dev": "pnpm --filter ./packages/* test:dev",
2325
"version-package": "changeset version"
2426
},
2527
"devDependencies": {

packages/toolkit-core/package.json

+5-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@zayne-labs/toolkit-core",
33
"type": "module",
44
"version": "0.8.39",
5-
"packageManager": "[email protected].4",
5+
"packageManager": "[email protected].5",
66
"description": "A collection of utility functions, types and composables used by my other projects. Nothing too fancy but can be useful.",
77
"author": "Ryan Zayne",
88
"license": "MIT",
@@ -37,11 +37,11 @@
3737
"build:dev": "cross-env NODE_ENV=development tsup",
3838
"build:test": "concurrently --prefix-colors \"yellow.bold,#7da4f8.bold,magenta\" --names PUBLINT,TSUP 'pnpm:lint:publint' 'pnpm:build:dev'",
3939
"lint:attw": "attw --pack . --ignore-rules=cjs-resolves-to-esm",
40-
"lint:check-types": "tsc --pretty --incremental -p tsconfig.json",
4140
"lint:publint": "publint --strict .",
4241
"lint:size": "size-limit",
42+
"lint:type-check": "tsc --pretty --incremental -p tsconfig.json",
4343
"release": "pnpm publish --no-git-checks",
44-
"test:release": "pnpx pkg-pr-new publish"
44+
"release:test": "pnpx pkg-pr-new publish"
4545
},
4646
"dependencies": {
4747
"@zayne-labs/toolkit-type-helpers": "workspace:*"
@@ -61,7 +61,8 @@
6161
"size-limit": "^11.2.0",
6262
"terser": "^5.39.0",
6363
"tsup": "^8.3.6",
64-
"typescript": "5.7.3"
64+
"typescript": "5.7.3",
65+
"vitest": "^3.0.6"
6566
},
6667
"publishConfig": {
6768
"access": "public",

packages/toolkit-core/src/omitKeys.ts

+16-13
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import type { AnyObject } from "@zayne-labs/toolkit-type-helpers";
1+
import type { UnknownObject } from "@zayne-labs/toolkit-type-helpers";
22

3-
type OmitKeys<TKeys extends keyof TObject, TObject extends AnyObject> = Omit<TObject, TKeys>;
3+
type OmitKeys<TKeys extends keyof TObject, TObject extends UnknownObject> = Omit<TObject, TKeys>;
44

5-
export const omitKeys = <TObject extends AnyObject, const TOmitArray extends Array<keyof TObject>>(
5+
export const omitKeys = <TObject extends UnknownObject, const TOmitArray extends Array<keyof TObject>>(
66
initialObject: TObject,
77
keysToOmit: TOmitArray
88
) => {
9-
const updatedObject = {} as AnyObject;
9+
const updatedObject = {} as UnknownObject;
1010

1111
const keysToOmitSet = new Set(keysToOmit);
1212

@@ -20,27 +20,30 @@ export const omitKeys = <TObject extends AnyObject, const TOmitArray extends Arr
2020
};
2121

2222
export const omitKeysWithReduce = <
23-
TObject extends AnyObject,
23+
TObject extends UnknownObject,
2424
const TOmitArray extends Array<keyof TObject>,
2525
>(
2626
initialObject: TObject,
2727
keysToOmit: TOmitArray
2828
) => {
2929
const keysToOmitSet = new Set(keysToOmit);
3030

31-
const updatedObject = Object.entries(initialObject).reduce<AnyObject>((accumulator, [key, value]) => {
32-
if (!keysToOmitSet.has(key)) {
33-
accumulator[key] = value;
34-
}
31+
const updatedObject = Object.entries(initialObject).reduce<UnknownObject>(
32+
(accumulator, [key, value]) => {
33+
if (!keysToOmitSet.has(key)) {
34+
accumulator[key] = value;
35+
}
3536

36-
return accumulator;
37-
}, {});
37+
return accumulator;
38+
},
39+
{}
40+
);
3841

3942
return updatedObject as OmitKeys<TOmitArray[number], TObject>;
4043
};
4144

4245
export const omitKeysWithFilter = <
43-
TObject extends AnyObject,
46+
TObject extends UnknownObject,
4447
const TOmitArray extends Array<keyof TObject>,
4548
>(
4649
initialObject: TObject,
@@ -58,7 +61,7 @@ export const omitKeysWithFilter = <
5861
};
5962

6063
export const omitKeysWithDelete = <
61-
TObject extends AnyObject,
64+
TObject extends UnknownObject,
6265
const TOmitArray extends Array<keyof TObject>,
6366
>(
6467
initialObject: TObject,

packages/toolkit-core/src/pickKeys.ts

+15-12
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import type { AnyObject } from "@zayne-labs/toolkit-type-helpers";
1+
import type { UnknownObject } from "@zayne-labs/toolkit-type-helpers";
22

3-
type PickKeys<TKeys extends keyof TObject, TObject extends AnyObject> = Pick<TObject, TKeys>;
3+
type PickKeys<TKeys extends keyof TObject, TObject extends UnknownObject> = Pick<TObject, TKeys>;
44

5-
export const pickKeys = <TObject extends AnyObject, const TPickArray extends Array<keyof TObject>>(
5+
export const pickKeys = <TObject extends UnknownObject, const TPickArray extends Array<keyof TObject>>(
66
initialObject: TObject,
77
keysToPick: TPickArray
88
) => {
9-
const updatedObject = {} as AnyObject;
9+
const updatedObject = {} as UnknownObject;
1010

1111
const keysToPickSet = new Set(keysToPick);
1212

@@ -20,27 +20,30 @@ export const pickKeys = <TObject extends AnyObject, const TPickArray extends Arr
2020
};
2121

2222
export const pickKeysWithReduce = <
23-
TObject extends AnyObject,
23+
TObject extends UnknownObject,
2424
const TPickArray extends Array<keyof TObject>,
2525
>(
2626
initialObject: TObject,
2727
keysToPick: TPickArray
2828
) => {
2929
const keysToPickSet = new Set(keysToPick);
3030

31-
const updatedObject = Object.entries(initialObject).reduce<AnyObject>((accumulator, [key, value]) => {
32-
if (keysToPickSet.has(key)) {
33-
accumulator[key] = value;
34-
}
31+
const updatedObject = Object.entries(initialObject).reduce<UnknownObject>(
32+
(accumulator, [key, value]) => {
33+
if (keysToPickSet.has(key)) {
34+
accumulator[key] = value;
35+
}
3536

36-
return accumulator;
37-
}, {});
37+
return accumulator;
38+
},
39+
{}
40+
);
3841

3942
return updatedObject as PickKeys<TPickArray[number], TObject>;
4043
};
4144

4245
export const pickKeysWithFilter = <
43-
TObject extends AnyObject,
46+
TObject extends UnknownObject,
4447
const TPickArray extends Array<keyof TObject>,
4548
>(
4649
initialObject: TObject,

packages/toolkit-core/src/syncStateWithStorage.ts

+18-9
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,33 @@
1-
import { isArray, isPlainObject } from "@zayne-labs/toolkit-type-helpers";
1+
import { type ExtractUnion, isArray, isPlainObject } from "@zayne-labs/toolkit-type-helpers";
22
import { pickKeys } from "./pickKeys";
33

4-
type SyncStorageParams =
5-
| [key: string, state: Record<string, unknown>, keysToSelect: string[]]
6-
| [key: string, state: Record<string, unknown> | unknown[]]
7-
| [key: string, state: string];
4+
// prettier-ignore
5+
type PossibleSyncStorageParams<
6+
TKey = string,
7+
TState = string | Record<string, unknown> | unknown[],
8+
TPickKeys = string[],
9+
> = [
10+
[key: TKey, state: TState, keysToSelect: TPickKeys],
11+
[key: TKey, state: TState]
12+
];
813

914
type SyncStateWithStorage = {
1015
<TKey extends string, TCompositeState extends Record<string, unknown> | unknown[]>(
11-
...params: [key: TKey, state: TCompositeState]
16+
...params: PossibleSyncStorageParams<TKey, TCompositeState>[1]
1217
): void;
1318

1419
<TKey extends string, TObject extends Record<string, unknown>, TPickArray extends Array<keyof TObject>>(
15-
...params: [key: TKey, state: TObject, keysToSelect: TPickArray]
20+
...params: PossibleSyncStorageParams<TKey, TObject, TPickArray>[0]
1621
): void;
1722

18-
<TKey extends string, TStringState extends string>(...params: [key: TKey, state: TStringState]): void;
23+
<TKey extends string, TStringState extends string>(
24+
...params: PossibleSyncStorageParams<TKey, TStringState>[1]
25+
): void;
1926
};
2027

21-
const syncStateWithStorage: SyncStateWithStorage = (...params: SyncStorageParams): void => {
28+
const syncStateWithStorage: SyncStateWithStorage = (
29+
...params: ExtractUnion<PossibleSyncStorageParams>
30+
): void => {
2231
const [storageKey, state, keysToOmit] = params;
2332

2433
switch (true) {
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { defineConfig } from "vitest/config";
2+
3+
export default defineConfig({
4+
test: {
5+
environment: "happy-dom",
6+
},
7+
});

packages/toolkit-react/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@zayne-labs/toolkit-react",
33
"type": "module",
44
"version": "0.8.39",
5-
"packageManager": "[email protected].4",
5+
"packageManager": "[email protected].5",
66
"description": "A collection of utility functions, types and composables used by my other projects. Nothing too fancy but can be useful.",
77
"author": "Ryan Zayne",
88
"license": "MIT",
@@ -39,11 +39,11 @@
3939
"build:dev": "cross-env NODE_ENV=development tsup",
4040
"build:test": "concurrently --prefix-colors \"yellow.bold,#7da4f8.bold,magenta\" --names PUBLINT,TSUP 'pnpm:lint:publint' 'pnpm:build:dev'",
4141
"lint:attw": "attw --pack . --ignore-rules=cjs-resolves-to-esm",
42-
"lint:check-types": "tsc --pretty --incremental -p tsconfig.json",
4342
"lint:publint": "publint --strict .",
4443
"lint:size": "size-limit",
44+
"lint:type-check": "tsc --pretty --incremental -p tsconfig.json",
4545
"release": "pnpm publish --no-git-checks",
46-
"test:release": "pnpx pkg-pr-new publish"
46+
"release:test": "pnpx pkg-pr-new publish"
4747
},
4848
"peerDependencies": {
4949
"react": ">=18.0.0",
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Prettify } from "@zayne-labs/toolkit-type-helpers";
1+
import type { AnyObject, Prettify } from "@zayne-labs/toolkit-type-helpers";
22

33
export type AsProp<TElement extends React.ElementType> = { as?: TElement };
44

@@ -10,11 +10,11 @@ type InferRestOfProps<TElement extends React.ElementType, TProps> = Omit<
1010

1111
// prettier-ignore
1212
// == Merge the AsProp and the TProps, or omit the AsProp if the user passed their own
13-
type MergedPropsWithAs<TElement extends React.ElementType, TProps> = Omit<AsProp<TElement>, keyof TProps> & TProps;
13+
type MergedPropsWithAs<TElement extends React.ElementType, TProps> = Prettify<Omit<AsProp<TElement>, keyof TProps> & TProps>;
1414

1515
// == Polymorphic props helper
1616
export type PolymorphicProps<
1717
TElement extends React.ElementType,
18-
// eslint-disable-next-line ts-eslint/no-explicit-any -- Any is needed so one can pass any prop type without type errors
19-
TProps extends Record<keyof any, any> = NonNullable<unknown>,
20-
> = InferRestOfProps<TElement, TProps> & Prettify<MergedPropsWithAs<TElement, TProps>>;
18+
TProps extends AnyObject = NonNullable<unknown>,
19+
// eslint-disable-next-line perfectionist/sort-intersection-types -- Let first one be first
20+
> = MergedPropsWithAs<TElement, TProps> & InferRestOfProps<TElement, TProps>;

packages/toolkit-type-helpers/package.json

+7-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@zayne-labs/toolkit-type-helpers",
33
"type": "module",
44
"version": "0.8.39",
5-
"packageManager": "[email protected].4",
5+
"packageManager": "[email protected].5",
66
"description": "A collection of utility functions, types and composables used by my other projects. Nothing too fancy but can be useful.",
77
"author": "Ryan Zayne",
88
"license": "MIT",
@@ -37,11 +37,13 @@
3737
"build:dev": "cross-env NODE_ENV=development tsup",
3838
"build:test": "concurrently --prefix-colors \"yellow.bold,#7da4f8.bold,magenta\" --names PUBLINT,TSUP 'pnpm:lint:publint' 'pnpm:build:dev'",
3939
"lint:attw": "attw --pack . --ignore-rules=cjs-resolves-to-esm",
40-
"lint:check-types": "tsc --pretty --incremental -p tsconfig.json",
4140
"lint:publint": "publint --strict .",
4241
"lint:size": "size-limit",
42+
"lint:type-check": "tsc --pretty --incremental -p tsconfig.json",
4343
"release": "pnpm publish --no-git-checks",
44-
"test:release": "pnpx pkg-pr-new publish"
44+
"release:test": "pnpx pkg-pr-new publish",
45+
"test": "vitest run",
46+
"test:dev": "vitest dev"
4547
},
4648
"devDependencies": {
4749
"@arethetypeswrong/cli": "^0.17.3",
@@ -58,7 +60,8 @@
5860
"size-limit": "^11.2.0",
5961
"terser": "^5.39.0",
6062
"tsup": "^8.3.6",
61-
"typescript": "5.7.3"
63+
"typescript": "5.7.3",
64+
"vitest": "^3.0.6"
6265
},
6366
"publishConfig": {
6467
"access": "public",
+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import type { Prettify, Writeable, WriteableLevel } from "./type-utils";
1+
import type { Prettify, Writeable, WriteableVariantUnion } from "./type-utils";
22

3-
export const defineEnum = <const TValue, TWriteableLevel extends WriteableLevel = "shallow">(
3+
export const defineEnum = <const TValue, TVariant extends WriteableVariantUnion = "shallow">(
44
value: TValue
5-
) => value as Prettify<Writeable<TValue, TWriteableLevel>>;
5+
) => {
6+
return value as Prettify<Writeable<TValue, TVariant>>;
7+
};

0 commit comments

Comments
 (0)