@cult-frog/primitives 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +82 -0
- package/dist/callbacks/constant.d.ts +21 -0
- package/dist/callbacks/constant.d.ts.map +1 -0
- package/dist/callbacks/constant.js +23 -0
- package/dist/callbacks/constant.js.map +1 -0
- package/dist/callbacks/identity.d.ts +20 -0
- package/dist/callbacks/identity.d.ts.map +1 -0
- package/dist/callbacks/identity.js +22 -0
- package/dist/callbacks/identity.js.map +1 -0
- package/dist/callbacks/index.d.ts +5 -0
- package/dist/callbacks/index.d.ts.map +1 -0
- package/dist/callbacks/index.js +5 -0
- package/dist/callbacks/index.js.map +1 -0
- package/dist/callbacks/noop.d.ts +16 -0
- package/dist/callbacks/noop.d.ts.map +1 -0
- package/dist/callbacks/noop.js +18 -0
- package/dist/callbacks/noop.js.map +1 -0
- package/dist/callbacks/once.d.ts +28 -0
- package/dist/callbacks/once.d.ts.map +1 -0
- package/dist/callbacks/once.js +38 -0
- package/dist/callbacks/once.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/predicates/index.d.ts +7 -0
- package/dist/predicates/index.d.ts.map +1 -0
- package/dist/predicates/index.js +7 -0
- package/dist/predicates/index.js.map +1 -0
- package/dist/predicates/nil.d.ts +31 -0
- package/dist/predicates/nil.d.ts.map +1 -0
- package/dist/predicates/nil.js +35 -0
- package/dist/predicates/nil.js.map +1 -0
- package/dist/predicates/null.d.ts +29 -0
- package/dist/predicates/null.d.ts.map +1 -0
- package/dist/predicates/null.js +33 -0
- package/dist/predicates/null.js.map +1 -0
- package/dist/predicates/number.d.ts +20 -0
- package/dist/predicates/number.d.ts.map +1 -0
- package/dist/predicates/number.js +22 -0
- package/dist/predicates/number.js.map +1 -0
- package/dist/predicates/record.d.ts +59 -0
- package/dist/predicates/record.d.ts.map +1 -0
- package/dist/predicates/record.js +69 -0
- package/dist/predicates/record.js.map +1 -0
- package/dist/predicates/string.d.ts +15 -0
- package/dist/predicates/string.d.ts.map +1 -0
- package/dist/predicates/string.js +17 -0
- package/dist/predicates/string.js.map +1 -0
- package/dist/predicates/undefined.d.ts +29 -0
- package/dist/predicates/undefined.d.ts.map +1 -0
- package/dist/predicates/undefined.js +33 -0
- package/dist/predicates/undefined.js.map +1 -0
- package/package.json +68 -0
- package/src/callbacks/constant.test.ts +31 -0
- package/src/callbacks/constant.ts +22 -0
- package/src/callbacks/identity.test.ts +24 -0
- package/src/callbacks/identity.ts +21 -0
- package/src/callbacks/index.ts +4 -0
- package/src/callbacks/noop.test.ts +19 -0
- package/src/callbacks/noop.ts +17 -0
- package/src/callbacks/once.test.ts +93 -0
- package/src/callbacks/once.ts +41 -0
- package/src/index.ts +2 -0
- package/src/predicates/index.ts +6 -0
- package/src/predicates/nil.test.ts +43 -0
- package/src/predicates/nil.ts +35 -0
- package/src/predicates/null.test.ts +42 -0
- package/src/predicates/null.ts +33 -0
- package/src/predicates/number.test.ts +38 -0
- package/src/predicates/number.ts +21 -0
- package/src/predicates/record.test.ts +107 -0
- package/src/predicates/record.ts +74 -0
- package/src/predicates/string.test.ts +25 -0
- package/src/predicates/string.ts +16 -0
- package/src/predicates/undefined.test.ts +42 -0
- package/src/predicates/undefined.ts +33 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if a value is `null`. `undefined` is not considered `null`; use {@link isNil} to check
|
|
3
|
+
* for both.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* isNull(null); // true
|
|
8
|
+
* isNull(undefined); // false
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* @param value - The value to check.
|
|
12
|
+
* @returns `true` if `value` is `null`, otherwise `false`.
|
|
13
|
+
*/
|
|
14
|
+
export function isNull<T>(value: T | null): value is null {
|
|
15
|
+
return value === null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Checks if a value is not `null`, narrowing `null` out of its type. `undefined` passes this
|
|
20
|
+
* check; use {@link isNotNil} to exclude both.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const rows: (Row | null)[] = await fetchRows();
|
|
25
|
+
* const found = rows.filter(isNotNull); // Row[]
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @param value - The value to check.
|
|
29
|
+
* @returns `true` if `value` is not `null`, otherwise `false`.
|
|
30
|
+
*/
|
|
31
|
+
export function isNotNull<T>(value: T | null): value is T {
|
|
32
|
+
return value !== null;
|
|
33
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, expectTypeOf, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { isNumber } from './number.js';
|
|
4
|
+
|
|
5
|
+
describe('isNumber', () => {
|
|
6
|
+
it.each([0, -0, 1, -1, 1.5, Number.MAX_SAFE_INTEGER, Number.MIN_VALUE])(
|
|
7
|
+
'returns true for %s',
|
|
8
|
+
(value) => {
|
|
9
|
+
expect(isNumber(value)).toBe(true);
|
|
10
|
+
}
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
it.each([Number.NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY])(
|
|
14
|
+
'returns false for non-finite %s',
|
|
15
|
+
(value) => {
|
|
16
|
+
expect(isNumber(value)).toBe(false);
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
it.each(['42', '', null, undefined, true, 10n, {}, []])(
|
|
21
|
+
'returns false for non-number %s without coercion',
|
|
22
|
+
(value) => {
|
|
23
|
+
expect(isNumber(value)).toBe(false);
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
it('returns false for Number wrapper objects', () => {
|
|
28
|
+
expect(isNumber(new Number(1))).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('narrows unknown to number', () => {
|
|
32
|
+
const value: unknown = 1;
|
|
33
|
+
|
|
34
|
+
if (isNumber(value)) {
|
|
35
|
+
expectTypeOf(value).toEqualTypeOf<number>();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if a value is a finite number.
|
|
3
|
+
*
|
|
4
|
+
* `NaN`, `Infinity` and `-Infinity` are rejected, as are numeric strings such as `'42'`; no
|
|
5
|
+
* coercion is performed.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* isNumber(42); // true
|
|
10
|
+
* isNumber(-1.5); // true
|
|
11
|
+
* isNumber(NaN); // false
|
|
12
|
+
* isNumber(Infinity); // false
|
|
13
|
+
* isNumber('42'); // false
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @param value - The value to check.
|
|
17
|
+
* @returns `true` if `value` is a finite number, otherwise `false`.
|
|
18
|
+
*/
|
|
19
|
+
export function isNumber(value: unknown): value is number {
|
|
20
|
+
return Number.isFinite(value);
|
|
21
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { runInNewContext } from 'node:vm';
|
|
3
|
+
import { describe, expect, expectTypeOf, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { hasProperty, isNotRecord, isRecord } from './record.js';
|
|
6
|
+
|
|
7
|
+
class Example {
|
|
8
|
+
value = 1;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const records = [{}, { id: 1 }, Object.create(null), JSON.parse('{"a":1}')];
|
|
12
|
+
const nonRecords = [
|
|
13
|
+
null,
|
|
14
|
+
undefined,
|
|
15
|
+
0,
|
|
16
|
+
'text',
|
|
17
|
+
true,
|
|
18
|
+
[],
|
|
19
|
+
new Date(),
|
|
20
|
+
new Map(),
|
|
21
|
+
new Set(),
|
|
22
|
+
/regex/,
|
|
23
|
+
new Example(),
|
|
24
|
+
Promise.resolve(),
|
|
25
|
+
() => {},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
describe('isRecord', () => {
|
|
29
|
+
it.each(records)('returns true for plain object %o', (value) => {
|
|
30
|
+
expect(isRecord(value)).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it.each(nonRecords)('returns false for %o', (value) => {
|
|
34
|
+
expect(isRecord(value)).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('returns false for objects from another realm', () => {
|
|
38
|
+
expect(isRecord(runInNewContext('({})'))).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('narrows unknown to Record<PropertyKey, unknown>', () => {
|
|
42
|
+
const value: unknown = {};
|
|
43
|
+
|
|
44
|
+
if (isRecord(value)) {
|
|
45
|
+
expectTypeOf(value).toEqualTypeOf<Record<PropertyKey, unknown>>();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('isNotRecord', () => {
|
|
51
|
+
it.each(records)('returns false for plain object %o', (value) => {
|
|
52
|
+
expect(isNotRecord(value)).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it.each(nonRecords)('returns true for %o', (value) => {
|
|
56
|
+
expect(isNotRecord(value)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('narrows the record out of a union', () => {
|
|
60
|
+
const value = 'text' as string | Record<string, unknown>;
|
|
61
|
+
|
|
62
|
+
if (isNotRecord(value)) {
|
|
63
|
+
expectTypeOf(value).toEqualTypeOf<string>();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('hasProperty', () => {
|
|
69
|
+
it('returns true for own properties', () => {
|
|
70
|
+
expect(hasProperty({ id: 1 }, 'id')).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('returns true for own properties holding undefined', () => {
|
|
74
|
+
expect(hasProperty({ id: undefined }, 'id')).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('returns false for missing properties', () => {
|
|
78
|
+
expect(hasProperty({ id: 1 }, 'name')).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('returns false for inherited properties', () => {
|
|
82
|
+
expect(hasProperty({}, 'toString')).toBe(false);
|
|
83
|
+
expect(hasProperty(Object.create({ inherited: true }), 'inherited')).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('supports symbol and numeric keys', () => {
|
|
87
|
+
const key = Symbol('key');
|
|
88
|
+
|
|
89
|
+
expect(hasProperty({ [key]: 1 }, key)).toBe(true);
|
|
90
|
+
expect(hasProperty(['a'], 0)).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('works on objects without a prototype', () => {
|
|
94
|
+
const value = Object.create(null) as object;
|
|
95
|
+
Object.assign(value, { id: 1 });
|
|
96
|
+
|
|
97
|
+
expect(hasProperty(value, 'id')).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('narrows the object so the property can be read', () => {
|
|
101
|
+
const value: object = { id: 1 };
|
|
102
|
+
|
|
103
|
+
if (hasProperty(value, 'id')) {
|
|
104
|
+
expectTypeOf(value.id).toEqualTypeOf<unknown>();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if a value is a plain object, i.e. an object literal or an object created with
|
|
3
|
+
* `Object.create(null)`.
|
|
4
|
+
*
|
|
5
|
+
* Arrays, class instances and built-ins such as `Date`, `Map` and `RegExp` are rejected. This
|
|
6
|
+
* makes it suitable for validating parsed data such as JSON before reading its properties.
|
|
7
|
+
*
|
|
8
|
+
* Objects created in another realm (an iframe or a Node `vm` context) are also rejected, because
|
|
9
|
+
* their prototype is a different `Object.prototype`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* isRecord({ id: 1 }); // true
|
|
14
|
+
* isRecord(Object.create(null)); // true
|
|
15
|
+
* isRecord([]); // false
|
|
16
|
+
* isRecord(new Date()); // false
|
|
17
|
+
* isRecord(null); // false
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @param value - The value to check.
|
|
21
|
+
* @returns `true` if `value` is a plain object, otherwise `false`.
|
|
22
|
+
*/
|
|
23
|
+
export function isRecord(value: unknown): value is Record<PropertyKey, unknown> {
|
|
24
|
+
if (typeof value !== 'object' || value === null) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const prototype: unknown = Object.getPrototypeOf(value);
|
|
29
|
+
return prototype === Object.prototype || prototype === null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Checks if a value is not a plain object. This is the inverse of {@link isRecord}.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* isNotRecord(new Date()); // true
|
|
38
|
+
* isNotRecord([]); // true
|
|
39
|
+
* isNotRecord({ id: 1 }); // false
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @param value - The value to check.
|
|
43
|
+
* @returns `true` if `value` is not a plain object, otherwise `false`.
|
|
44
|
+
*/
|
|
45
|
+
export function isNotRecord<T>(value: T): value is Exclude<T, Record<PropertyKey, unknown>> {
|
|
46
|
+
return !isRecord(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Checks if an object has the given key as its own property, narrowing the object so the
|
|
51
|
+
* property can be read.
|
|
52
|
+
*
|
|
53
|
+
* Inherited properties are not counted, so keys such as `toString` do not match. The property
|
|
54
|
+
* may still hold `undefined`.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* const data: unknown = JSON.parse(input);
|
|
59
|
+
*
|
|
60
|
+
* if (isRecord(data) && hasProperty(data, 'id') && isNumber(data.id)) {
|
|
61
|
+
* data.id; // number
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @param value - The object to check.
|
|
66
|
+
* @param key - The property key to look for.
|
|
67
|
+
* @returns `true` if `value` has `key` as an own property, otherwise `false`.
|
|
68
|
+
*/
|
|
69
|
+
export function hasProperty<K extends PropertyKey>(
|
|
70
|
+
value: object,
|
|
71
|
+
key: K
|
|
72
|
+
): value is Record<K, unknown> {
|
|
73
|
+
return Object.hasOwn(value, key);
|
|
74
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, expectTypeOf, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { isString } from './string.js';
|
|
4
|
+
|
|
5
|
+
describe('isString', () => {
|
|
6
|
+
it.each(['', 'text', `template`])('returns true for %j', (value) => {
|
|
7
|
+
expect(isString(value)).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it.each([0, null, undefined, true, {}, [], Symbol('s')])('returns false for %s', (value) => {
|
|
11
|
+
expect(isString(value)).toBe(false);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('returns false for String wrapper objects', () => {
|
|
15
|
+
expect(isString(new String('text'))).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('narrows unknown to string', () => {
|
|
19
|
+
const value: unknown = 'text';
|
|
20
|
+
|
|
21
|
+
if (isString(value)) {
|
|
22
|
+
expectTypeOf(value).toEqualTypeOf<string>();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if a value is a string primitive. `String` wrapper objects are rejected.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* isString('hello'); // true
|
|
7
|
+
* isString(''); // true
|
|
8
|
+
* isString(42); // false
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* @param value - The value to check.
|
|
12
|
+
* @returns `true` if `value` is a string, otherwise `false`.
|
|
13
|
+
*/
|
|
14
|
+
export function isString(value: unknown): value is string {
|
|
15
|
+
return typeof value === 'string';
|
|
16
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, expectTypeOf, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { isNotUndefined, isUndefined } from './undefined.js';
|
|
4
|
+
|
|
5
|
+
const definedValues = [null, 0, '', false, {}, 'text'];
|
|
6
|
+
|
|
7
|
+
describe('isUndefined', () => {
|
|
8
|
+
it('returns true for undefined', () => {
|
|
9
|
+
expect(isUndefined(undefined)).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it.each(definedValues)('returns false for %s', (value) => {
|
|
13
|
+
expect(isUndefined(value)).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('narrows to undefined', () => {
|
|
17
|
+
const value = 'text' as string | undefined;
|
|
18
|
+
|
|
19
|
+
if (isUndefined(value)) {
|
|
20
|
+
expectTypeOf(value).toEqualTypeOf<undefined>();
|
|
21
|
+
} else {
|
|
22
|
+
expectTypeOf(value).toEqualTypeOf<string>();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('isNotUndefined', () => {
|
|
28
|
+
it('returns false for undefined', () => {
|
|
29
|
+
expect(isNotUndefined(undefined)).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it.each(definedValues)('returns true for %s', (value) => {
|
|
33
|
+
expect(isNotUndefined(value)).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('removes undefined in filter but keeps null', () => {
|
|
37
|
+
const result = [1, null, undefined].filter(isNotUndefined);
|
|
38
|
+
|
|
39
|
+
expect(result).toEqual([1, null]);
|
|
40
|
+
expectTypeOf(result).toEqualTypeOf<(number | null)[]>();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if a value is `undefined`. `null` is not considered `undefined`; use {@link isNil} to
|
|
3
|
+
* check for both.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* isUndefined(undefined); // true
|
|
8
|
+
* isUndefined(null); // false
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* @param value - The value to check.
|
|
12
|
+
* @returns `true` if `value` is `undefined`, otherwise `false`.
|
|
13
|
+
*/
|
|
14
|
+
export function isUndefined<T>(value: T | undefined): value is undefined {
|
|
15
|
+
return value === undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Checks if a value is not `undefined`, narrowing `undefined` out of its type. `null` passes
|
|
20
|
+
* this check; use {@link isNotNil} to exclude both.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const lookups = ids.map((id) => cache.get(id)); // (User | undefined)[]
|
|
25
|
+
* const cached = lookups.filter(isNotUndefined); // User[]
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @param value - The value to check.
|
|
29
|
+
* @returns `true` if `value` is not `undefined`, otherwise `false`.
|
|
30
|
+
*/
|
|
31
|
+
export function isNotUndefined<T>(value: T | undefined): value is T {
|
|
32
|
+
return value !== undefined;
|
|
33
|
+
}
|