Skip to content

Commit

Permalink
chore(deps): update dependency @belgattitude/eslint-config-bases to v…
Browse files Browse the repository at this point in the history
…6.0.0-canary.14 (#1515)

* chore(deps): update dependency @belgattitude/eslint-config-bases to v6.0.0-canary.14

* fix: edge cases with typings in plain-object

* fix: edge cases with typings in plain-object

* fix: edge cases with typings in plain-object

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Sebastien Vanvelthem <[email protected]>
  • Loading branch information
renovate[bot] and Sebastien Vanvelthem authored Oct 8, 2024
1 parent 5ecbcc6 commit 2692a50
Show file tree
Hide file tree
Showing 22 changed files with 204 additions and 114 deletions.
5 changes: 5 additions & 0 deletions .changeset/bright-toys-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@httpx/assert": patch
---

Improve isPlainObject and base it on @httpx/plain-object
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"react-dom": "18.3.1"
},
"devDependencies": {
"@belgattitude/eslint-config-bases": "6.0.0-canary.13",
"@belgattitude/eslint-config-bases": "6.0.0-canary.14",
"eslint": "8.57.1",
"eslint-config-next": "14.2.14",
"eslint-plugin-tailwindcss": "3.17.4",
Expand Down
2 changes: 1 addition & 1 deletion integrations/prisma-exception/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "0.16.4",
"@belgattitude/eslint-config-bases": "6.0.0-canary.13",
"@belgattitude/eslint-config-bases": "6.0.0-canary.14",
"@size-limit/file": "11.1.6",
"@size-limit/webpack": "11.1.6",
"@size-limit/webpack-why": "11.1.6",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
],
"packageManager": "[email protected]",
"devDependencies": {
"@belgattitude/eslint-config-bases": "6.0.0-canary.13",
"@belgattitude/eslint-config-bases": "6.0.0-canary.14",
"@changesets/changelog-github": "0.5.0",
"@changesets/cli": "2.27.9",
"@commitlint/cli": "19.5.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/assert/.size-limit.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ module.exports = [
name: 'Only assertPlainObject (ESM)',
path: ['dist/index.mjs'],
import: "{ assertPlainObject }",
limit: "487B",
limit: "489B",
},
{
name: 'Everything (CJS)',
Expand Down
45 changes: 27 additions & 18 deletions packages/assert/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,31 +177,40 @@ switch(state) {
| isPlainObject\<T?\> | `PlainObject` | |
| assertPlainObject\<T?\> | `PlainObject` | |

> Inspired and compatible with [is-plain-obj](https://github.com/sindresorhus/is-plain-obj). Check the [test file](https://github.com/belgattitude/httpx/blob/main/packages/assert/src/__tests__/object.guards.test.ts)
> Based on [@httpx/plain-object](https://github.com/belgattitude/httpx/tree/main/packages/plain-object#readme)
```typescript

import { isPlainObject, assertPlainObject } from '@httpx/assert';

// Simple case: without generic value
isPlainObject({ }); // 👈 ✅ true
isPlainObject({ key: 'value' }); // 👈 ✅ true
isPlainObject({ key: new Date() }); // 👈 ✅ true
isPlainObject(new Object()); // 👈 ✅ true
isPlainObject(Object.create(null)); // 👈 ✅ true
isPlainObject({nested: { key: true} }); // 👈 ✅ true
isPlainObject(runInNewContext('({})')); // 👈 ✅ true
// ✅👇 True

class Test { };
isPlainObject({ key: 'value' }); //
isPlainObject({ key: new Date() }); //
isPlainObject(new Object()); //
isPlainObject(Object.create(null)); //
isPlainObject({ nested: { key: true} }); //
isPlainObject(new Proxy({}, {})); //
isPlainObject({ [Symbol('tag')]: 'A' }); //

// ✅👇 (node context, workers, ...)
const runInNewContext = await import('node:vm').then(
(mod) => mod.runInNewContext
);
isPlainObject(runInNewContext('({})')); //

isPlainObject(new Test()) // 👈 ❌ false
isPlainObject(10); // 👈 ❌ false
isPlainObject(null); // 👈 ❌ false
isPlainObject('hello'); // 👈 ❌ false
isPlainObject([]); // 👈 ❌ false
isPlainObject(new Date()); // 👈 ❌ false
isPlainObject(Math); // 👈 ❌ false
// (... see test file)
// ❌👇 False

class Test { };
isPlainObject(new Test()) //
isPlainObject(10); //
isPlainObject(null); //
isPlainObject('hello'); //
isPlainObject([]); //
isPlainObject(new Date()); //
isPlainObject(Math); // ❌ Static built-in classes
isPlainObject(Promise.resolve({})); //
isPlainObject(Object.create({})); //

assertPlainObject({}) // 👈 ✅ true

Expand Down
2 changes: 1 addition & 1 deletion packages/assert/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "0.16.4",
"@belgattitude/eslint-config-bases": "6.0.0-canary.13",
"@belgattitude/eslint-config-bases": "6.0.0-canary.14",
"@codspeed/vitest-plugin": "3.1.1",
"@edge-runtime/vm": "4.0.3",
"@sinclair/typebox": "0.33.15",
Expand Down
16 changes: 14 additions & 2 deletions packages/assert/src/__tests__/object.guards.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,25 @@ describe('Object typeguards tests', () => {
},
false,
],
// globalThis
[globalThis, false],
// Static built-in classes
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
[JSON, false],
[Math, false],
[Atomics, false],
[JSON, false],
// built-in classes
// Built-in classes
[new Date(), false],
[new Map(), false],
[new Error(), false],
[new Set(), false],
[new Request('http://localhost'), false],
[new Promise(() => {}), false],
[Promise.resolve({}), false],
// eslint-disable-next-line no-restricted-globals
[Buffer.from('123123'), false],
[new Uint8Array([1, 2, 3]), false],
[Object.create({}), false],
[/(\d+)/, false],
// eslint-disable-next-line prefer-regex-literals
Expand Down Expand Up @@ -109,8 +114,15 @@ describe('Object typeguards tests', () => {
);
});
});

describe('Support node:vm.runInNewContext', () => {
const isNodeLike = typeof window === 'undefined';
const isNodeLike = 'window' in globalThis;
/*
const isNode = () =>
typeof process !== 'undefined' &&
!!process.versions &&
!!process.versions.node;
*/
it.skipIf(!isNodeLike)('should support vm', async () => {
// eslint-disable-next-line import-x/no-nodejs-modules
const runInNewContext = await import('node:vm').then(
Expand Down
2 changes: 2 additions & 0 deletions packages/assert/src/__tests__/object.types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ describe('object types tests', () => {
// act
assertPlainObject(unknownPo);

assertType<PlainObject>(unknownPo);

// Now the type is Record<string, unknown>, javascript allows to retrieve it
// even if it doesn't exist. The value should be undefined|unknown
const invalidKeyIsUnknown = unknownPo.invalidKey;
Expand Down
12 changes: 0 additions & 12 deletions packages/assert/src/messages/getTypeInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,3 @@ export const getTypeInfo = (v: unknown): string => {
}
return type;
};
const vowelsAndH = new Set(['a', 'e', 'i', 'o', 'u', 'y', 'h']);
export const formatErrMsg = (
msg: string,
v: unknown,
options?: {
pfx: boolean;
}
): string => {
const { pfx = true } = options ?? {};
const aOrAn = vowelsAndH.has((msg?.[0] ?? '').toLowerCase()) ? 'an' : 'a';
return `${pfx ? `${errPfx} ${aOrAn} ` : ''}${msg}, got: ${getTypeInfo(v)}`;
};
38 changes: 34 additions & 4 deletions packages/assert/src/object.asserts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,52 @@ import { formatErrMsg } from './messages/errorMessages';
import { isPlainObject } from './object.guards';
import type {
BasePlainObject,
UnspecifiedPlainObjectType,
DefaultBasePlainObject,
} from './object.internal.types';
import type { PlainObject } from './object.types';
import type { MsgOrErrorFactory } from './types/internal.types';
import { createAssertException } from './utils/createAssertException';

/**
* Assert a value is a plain object
*
* @example
* ```typescript
* import { assertPlainObject } from '@httpx/plain-object';
* import type { PlainObject } from '@httpx/plain-object';
*
* function fn(value: unknown) {
*
* // 👇 Throws `new TypeError('Not a plain object')` if not a plain object
* assertPlainObject(value);
*
* // 👇 Throws `new TypeError('Custom message')` if not a plain object
* assertPlainObject(value, 'Custom message');
*
* // 👇 Throws custom error if not a plain object
* assertPlainObject(value, () => {
* throw new HttpBadRequest('Custom message');
* });
*
* return value;
* }
*
* try {
* const value = fn({ key: 'value' });
* // ✅ Value is known to be PlainObject<unknown>
* assertType<PlainObject>(value);
* } catch (error) {
* console.error(error);
* }
* ```
*
* @throws TypeError
*/
export function assertPlainObject<
TValue extends Record<string, unknown> = UnspecifiedPlainObjectType,
TValue extends BasePlainObject = DefaultBasePlainObject,
>(
v: unknown,
msgOrErrorFactory?: MsgOrErrorFactory
): asserts v is TValue extends UnspecifiedPlainObjectType
): asserts v is TValue extends DefaultBasePlainObject
? BasePlainObject
: PlainObject<TValue> {
if (!isPlainObject<TValue>(v)) {
Expand Down
50 changes: 33 additions & 17 deletions packages/assert/src/object.guards.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {
BasePlainObject,
UnspecifiedPlainObjectType,
DefaultBasePlainObject,
} from './object.internal.types';
import type { PlainObject } from './object.types';

Expand All @@ -11,28 +11,43 @@ import type { PlainObject } from './object.types';
*
* @example
* ```typescript
* isPlainObject({ key: 'value' }); // 👈 ✅ true
* isPlainObject({ key: new Date() }); // 👈 ✅ true
* isPlainObject(new Object()); // 👈 ✅ true
* isPlainObject(Object.create(null)); // 👈 ✅ true
* isPlainObject({nested: { key: true} } // 👈 ✅ true
* import { isPlainObject } from '@httpx/plain-object';
*
* class Test { };
* // ✅👇 True
*
* isPlainObject({ key: 'value' }); // ✅
* isPlainObject({ key: new Date() }); // ✅
* isPlainObject(new Object()); // ✅
* isPlainObject(Object.create(null)); // ✅
* isPlainObject({ nested: { key: true} }); // ✅
* isPlainObject(new Proxy({}, {})); // ✅
* isPlainObject({ [Symbol('tag')]: 'A' }); // ✅
*
* // ✅👇 (node context, workers, ...)
* const runInNewContext = await import('node:vm').then(
* (mod) => mod.runInNewContext
* );
* isPlainObject(runInNewContext('({})')); // ✅
*
* isPlainObject(new Test()) // 👈 ❌ false
* isPlainObject(10); // 👈 ❌ false
* isPlainObject(null); // 👈 ❌ false
* isPlainObject('hello'); // 👈 ❌ false
* isPlainObject([]); // 👈 ❌ false
* isPlainObject(new Date()); // 👈 ❌ false
* isPlainObject(Math); // 👈 ❌ false
* (...)
* // ❌👇 False
*
* class Test { };
* isPlainObject(new Test()) // ❌
* isPlainObject(10); // ❌
* isPlainObject(null); // ❌
* isPlainObject('hello'); // ❌
* isPlainObject([]); // ❌
* isPlainObject(new Date()); // ❌
* isPlainObject(Math); // ❌ Static built-in classes
* isPlainObject(Promise.resolve({})); // ❌
* isPlainObject(Object.create({})); // ❌
* ```
*/
export const isPlainObject = <
TValue extends Record<string, unknown> = UnspecifiedPlainObjectType,
TValue extends BasePlainObject = DefaultBasePlainObject,
>(
v: unknown
): v is TValue extends UnspecifiedPlainObjectType
): v is TValue extends DefaultBasePlainObject
? BasePlainObject
: PlainObject<TValue> => {
if (v === null || typeof v !== 'object') {
Expand All @@ -43,6 +58,7 @@ export const isPlainObject = <
return (
(proto === null ||
proto === Object.prototype ||
// Required to support node:vm.runInNewContext({})
Object.getPrototypeOf(proto) === null) &&
// https://stackoverflow.com/a/76387885/5490184
!(Symbol.toStringTag in v) &&
Expand Down
10 changes: 5 additions & 5 deletions packages/assert/src/object.internal.types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Simplify } from './types/internal.types';

export type PlainObjectKey = string;

export type UnspecifiedPlainObjectType = BasePlainObject & {
readonly __httpxInternalTag: '@httpx/PlainObject';
};
export type PlainObjectKey = string | number | symbol;

export type BasePlainObject = Record<PlainObjectKey, unknown>;

export interface DefaultBasePlainObject extends BasePlainObject {
readonly __tag: 'default-plain-object';
}

export type PlainObjectDeepPartialUnknown<T> = {
[P in keyof T]?: NonNullable<T[P]> extends BasePlainObject
? Simplify<PlainObjectDeepPartialUnknown<NonNullable<T[P]>>>
Expand Down
6 changes: 3 additions & 3 deletions packages/assert/src/object.types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type {
BasePlainObject,
DefaultBasePlainObject,
PlainObjectDeepPartialUnknown,
PlainObjectKey,
UnspecifiedPlainObjectType,
} from './object.internal.types';
import type { Simplify } from './types/internal.types';

export type PlainObject<
TValue extends BasePlainObject = UnspecifiedPlainObjectType,
> = TValue extends UnspecifiedPlainObjectType
TValue extends BasePlainObject = DefaultBasePlainObject,
> = TValue extends DefaultBasePlainObject
? Record<PlainObjectKey, unknown>
: Simplify<PlainObjectDeepPartialUnknown<TValue>>;
2 changes: 1 addition & 1 deletion packages/dsn-parser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "0.16.4",
"@belgattitude/eslint-config-bases": "6.0.0-canary.13",
"@belgattitude/eslint-config-bases": "6.0.0-canary.14",
"@codspeed/vitest-plugin": "3.1.1",
"@edge-runtime/vm": "4.0.3",
"@size-limit/file": "11.1.6",
Expand Down
2 changes: 1 addition & 1 deletion packages/exception/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "0.16.4",
"@belgattitude/eslint-config-bases": "6.0.0-canary.13",
"@belgattitude/eslint-config-bases": "6.0.0-canary.14",
"@codspeed/vitest-plugin": "3.1.1",
"@edge-runtime/vm": "4.0.3",
"@size-limit/file": "11.1.6",
Expand Down
2 changes: 1 addition & 1 deletion packages/json-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "0.16.4",
"@belgattitude/eslint-config-bases": "6.0.0-canary.13",
"@belgattitude/eslint-config-bases": "6.0.0-canary.14",
"@codspeed/vitest-plugin": "3.1.1",
"@edge-runtime/vm": "4.0.3",
"@size-limit/file": "11.1.6",
Expand Down
2 changes: 1 addition & 1 deletion packages/memo-intl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "0.16.4",
"@belgattitude/eslint-config-bases": "6.0.0-canary.13",
"@belgattitude/eslint-config-bases": "6.0.0-canary.14",
"@codspeed/vitest-plugin": "3.1.1",
"@edge-runtime/vm": "4.0.3",
"@size-limit/file": "11.1.6",
Expand Down
2 changes: 1 addition & 1 deletion packages/plain-object/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "0.16.4",
"@belgattitude/eslint-config-bases": "6.0.0-canary.13",
"@belgattitude/eslint-config-bases": "6.0.0-canary.14",
"@codspeed/vitest-plugin": "3.1.1",
"@edge-runtime/vm": "4.0.3",
"@sindresorhus/is": "7.0.1",
Expand Down
Loading

0 comments on commit 2692a50

Please sign in to comment.