1
0
mirror of https://github.com/robonen/tools.git synced 2026-03-20 02:44:45 +00:00

Merge branch 'stdlib-types' into platform

# Conflicts:
#	packages/stdlib/package.json
#	packages/stdlib/src/math/basic/remap/index.ts
This commit is contained in:
2024-09-30 06:30:24 +07:00
23 changed files with 839 additions and 8 deletions

View File

@@ -2,9 +2,8 @@ import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
rollup: {
emitCJS: true,
esbuild: {
minify: true,
// minify: true,
},
},
});

View File

@@ -0,0 +1,84 @@
type Exist<T> = T extends undefined | null ? never : T;
type ExtractFromObject<O extends Record<PropertyKey, unknown>, K> =
K extends keyof O
? O[K]
: K extends keyof Exist<O>
? Exist<O>[K]
: never;
type ExtractFromArray<A extends readonly any[], K> = any[] extends A
? A extends readonly (infer T)[]
? T | undefined
: undefined
: K extends keyof A
? A[K]
: undefined;
type GetWithArray<O, K> = K extends []
? O
: K extends [infer Key, ...infer Rest]
? O extends Record<PropertyKey, unknown>
? GetWithArray<ExtractFromObject<O, Key>, Rest>
: O extends readonly any[]
? GetWithArray<ExtractFromArray<O, Key>, Rest>
: never
: never;
type Path<T> = T extends `${infer Key}.${infer Rest}`
? [Key, ...Path<Rest>]
: T extends `${infer Key}`
? [Key]
: [];
// Type that generate a type of a value by a path;
// e.g. ['a', 'b', 'c'] => { a: { b: { c: PropertyKey } } }
// e.g. ['a', 'b', 'c', 'd'] => { a: { b: { c: { d: PropertyKey } } } }
// e.g. ['a'] => { a: PropertyKey }
// e.g. ['a', '0'], => { a: [PropertyKey] }
// e.g. ['a', '0', 'b'] => { a: [{ b: PropertyKey }] }
// e.g. ['a', '0', 'b', '0'] => { a: [{ b: [PropertyKey] }] }
// e/g/ ['0', 'a'] => [{ a: PropertyKey }]
//
// Input: ['a', 'b', 'c'], constrain: PropertyKey
// Output: { a: { b: { c: PropertyKey } } }
export type UnionToIntersection<Union> = (
Union extends unknown
? (distributedUnion: Union) => void
: never
) extends ((mergedIntersection: infer Intersection) => void)
? Intersection & Union
: never;
type PathToType<T extends string[]> = T extends [infer Head, ...infer Rest]
? Head extends string
? Head extends `${number}`
? Rest extends string[]
? PathToType<Rest>[]
: never
: Rest extends string[]
? { [K in Head & string]: PathToType<Rest> }
: never
: never
: string;
export type Generate<T extends string> = UnionToIntersection<PathToType<Path<T>>>;
type Get<O, K> = GetWithArray<O, Path<K>>;
export function getByPath<O, K extends string>(obj: O, path: K): Get<O, K>;
export function getByPath(obj: Record<string, unknown>, path: string): unknown {
const keys = path.split('.');
let currentObj = obj;
for (const key of keys) {
const value = currentObj[key];
if (value === undefined || value === null) return undefined;
currentObj = value as Record<string, unknown>;
}
return currentObj;
}

View File

@@ -0,0 +1 @@
export * from './getByPath';

View File

@@ -2,4 +2,6 @@ export * from './text';
export * from './math';
export * from './patterns';
export * from './bits';
export * from './structs';
export * from './structs';
export * from './arrays';
export * from './types';

View File

@@ -1,7 +1,7 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { PubSub } from './index';
describe('PubSub', () => {
describe('pubsub', () => {
let eventBus: PubSub<{
event1: (arg: string) => void;
event2: () => void

View File

@@ -1,7 +1,7 @@
import { describe, it, expect } from 'vitest';
import { Stack } from './index';
describe('Stack', () => {
describe('stack', () => {
describe('constructor', () => {
it('create an empty stack if no initial values are provided', () => {
const stack = new Stack<number>();

View File

@@ -1,2 +1,3 @@
export * from './levenshtein-distance';
export * from './trigram-distance';
export * from './trigram-distance';
export * from './template';

View File

@@ -0,0 +1,105 @@
import { describe, expectTypeOf, it } from "vitest";
import type { ClearPlaceholder, ExtractPlaceholders } from "./index";
describe('template', () => {
describe('ClearPlaceholder', () => {
it('ignores strings without braces', () => {
type actual = ClearPlaceholder<'name'>;
type expected = 'name';
expectTypeOf<actual>().toEqualTypeOf<expected>();
});
it('removes all balanced braces from placeholders', () => {
type actual1 = ClearPlaceholder<'{name}'>;
type actual2 = ClearPlaceholder<'{{name}}'>;
type actual3 = ClearPlaceholder<'{{{name}}}'>;
type expected = 'name';
expectTypeOf<actual1>().toEqualTypeOf<expected>();
expectTypeOf<actual2>().toEqualTypeOf<expected>();
expectTypeOf<actual3>().toEqualTypeOf<expected>();
});
it('removes all unbalanced braces from placeholders', () => {
type actual1 = ClearPlaceholder<'{name}}'>;
type actual2 = ClearPlaceholder<'{{name}}}'>;
type expected = 'name';
expectTypeOf<actual1>().toEqualTypeOf<expected>();
expectTypeOf<actual2>().toEqualTypeOf<expected>();
});
});
describe('ExtractPlaceholders', () => {
it('string without placeholders', () => {
type actual = ExtractPlaceholders<'Hello name, how are?'>;
type expected = never;
expectTypeOf<actual>().toEqualTypeOf<expected>();
});
it('string with one idexed placeholder', () => {
type actual = ExtractPlaceholders<'Hello {0}, how are you?'>;
type expected = '0';
expectTypeOf<actual>().toEqualTypeOf<expected>();
});
it('string with two indexed placeholders', () => {
type actual = ExtractPlaceholders<'Hello {0}, my name is {1}'>;
type expected = '0' | '1';
expectTypeOf<actual>().toEqualTypeOf<expected>();
});
it('string with one key placeholder', () => {
type actual = ExtractPlaceholders<'Hello {name}, how are you?'>;
type expected = 'name';
expectTypeOf<actual>().toEqualTypeOf<expected>();
});
it('string with two key placeholders', () => {
type actual = ExtractPlaceholders<'Hello {name}, my name is {managers.0.name}'>;
type expected = 'name' | 'managers.0.name';
expectTypeOf<actual>().toEqualTypeOf<expected>();
});
it('string with mixed placeholders', () => {
type actual = ExtractPlaceholders<'Hello {0}, how are you? My name is {1.name}'>;
type expected = '0' | '1.name';
expectTypeOf<actual>().toEqualTypeOf<expected>();
});
it('string with nested placeholder and balanced braces', () => {
type actual = ExtractPlaceholders<'Hello {{name}}, how are you?'>;
type expected = 'name';
expectTypeOf<actual>().toEqualTypeOf<expected>();
});
it('string with nested placeholder and unbalanced braces', () => {
type actual = ExtractPlaceholders<'Hello {{{name}, how are you?'>;
type expected = 'name';
expectTypeOf<actual>().toEqualTypeOf<expected>();
});
it('string with nested placeholders and balanced braces', () => {
type actual = ExtractPlaceholders<'Hello {{{name}{positions}}}, how are you?'>;
type expected = 'name' | 'positions';
expectTypeOf<actual>().toEqualTypeOf<expected>();
});
it('string with nested placeholders and unbalanced braces', () => {
type actual = ExtractPlaceholders<'Hello {{{name}{positions}, how are you?'>;
type expected = 'name' | 'positions';
expectTypeOf<actual>().toEqualTypeOf<expected>();
});
});
});

View File

@@ -0,0 +1,48 @@
import { describe, expect, it } from 'vitest';
import { templateObject } from './index';
describe('templateObject', () => {
// it('replace template placeholders with corresponding values from args', () => {
// const template = 'Hello, {names.0}!';
// const args = { names: ['John'] };
// const result = templateObject(template, args);
// expect(result).toBe('Hello, John!');
// });
// it('replace template placeholders with corresponding values from args', () => {
// const template = 'Hello, {name}!';
// const args = { name: 'John' };
// const result = templateObject(template, args);
// expect(result).toBe('Hello, John!');
// });
// it('replace template placeholders with fallback value if corresponding value is undefined', () => {
// const template = 'Hello, {name}!';
// const args = { age: 25 };
// const fallback = 'Guest';
// const result = templateObject(template, args, fallback);
// expect(result).toBe('Hello, Guest!');
// });
// it(' replace template placeholders with fallback value returned by fallback function if corresponding value is undefined', () => {
// const template = 'Hello, {name}!';
// const args = { age: 25 };
// const fallback = (key: string) => `Unknown ${key}`;
// const result = templateObject(template, args, fallback);
// expect(result).toBe('Hello, Unknown name!');
// });
it('replace template placeholders with nested values from args', () => {
const result = templateObject('Hello {{user.name}, your address {user.addresses.0.street}', {
user: {
name: 'John Doe',
addresses: [
{ street: '123 Main St', city: 'Springfield'},
{ street: '456 Elm St', city: 'Shelbyville'}
]
}
});
expect(result).toBe('Hello {John Doe, your address 123 Main St');
});
});

View File

@@ -0,0 +1,66 @@
import { getByPath, type Generate } from '../../arrays';
import { isFunction } from '../../types';
/**
* Type of a value that will be used to replace a placeholder in a template.
*/
type StringPrimitive = string | number | bigint | null | undefined;
/**
* Type of a fallback value when a template key is not found.
*/
type TemplateFallback = string | ((key: string) => string);
/**
* Type of an object that will be used to replace placeholders in a template.
*/
type TemplateArgsObject = StringPrimitive[] | { [key: string]: TemplateArgsObject | StringPrimitive };
/**
* Type of a template string with placeholders.
*/
const TEMPLATE_PLACEHOLDER = /{([^{}]+)}/gm;
/**
* Removes the placeholder syntax from a template string.
*
* @example
* type Base = ClearPlaceholder<'{user.name}'>; // 'user.name'
* type Unbalanced = ClearPlaceholder<'{user.name'>; // 'user.name'
*/
export type ClearPlaceholder<T extends string> =
T extends `${string}{${infer Template}`
? ClearPlaceholder<Template>
: T extends `${infer Template}}${string}`
? ClearPlaceholder<Template>
: T;
/**
* Extracts all placeholders from a template string.
*
* @example
* type Base = ExtractPlaceholders<'Hello {user.name}, {user.addresses.0.street}'>; // 'user.name' | 'user.addresses.0.street'
*/
export type ExtractPlaceholders<T extends string> =
T extends `${infer Before}}${infer After}`
? Before extends `${string}{${infer Placeholder}`
? ClearPlaceholder<Placeholder> | ExtractPlaceholders<After>
: ExtractPlaceholders<After>
: never;
export function templateObject<T extends string, A extends Generate<ExtractPlaceholders<T>>>(template: T, args: A, fallback?: TemplateFallback): string {
return template.replace(TEMPLATE_PLACEHOLDER, (_, key) => {
const value = getByPath(args, key) as string;
return value !== undefined ? value : (isFunction(fallback) ? fallback(key) : '');
});
}
templateObject('Hello {user.name}, your address {user.addresses.0.street}', {
user: {
name: 'John Doe',
addresses: [
{ street: '123 Main St', city: 'Springfield'},
{ street: '456 Elm St', city: 'Shelbyville'},
],
},
});

View File

@@ -0,0 +1,2 @@
export * from './js';
export * from './ts';

View File

@@ -0,0 +1,30 @@
import { describe, it, expect } from 'vitest';
import { toString } from './casts';
describe('casts', () => {
describe('toString', () => {
it('correct string representation of a value', () => {
// Primitives
expect(toString(true)).toBe('[object Boolean]');
expect(toString(() => {})).toBe('[object Function]');
expect(toString(5)).toBe('[object Number]');
expect(toString(BigInt(5))).toBe('[object BigInt]');
expect(toString('hello')).toBe('[object String]');
expect(toString(Symbol('foo'))).toBe('[object Symbol]');
// Complex
expect(toString([])).toBe('[object Array]');
expect(toString({})).toBe('[object Object]');
expect(toString(undefined)).toBe('[object Undefined]');
expect(toString(null)).toBe('[object Null]');
expect(toString(/abc/)).toBe('[object RegExp]');
expect(toString(new Date())).toBe('[object Date]');
expect(toString(new Error())).toBe('[object Error]');
expect(toString(new Promise(() => {}))).toBe('[object Promise]');
expect(toString(new Map())).toBe('[object Map]');
expect(toString(new Set())).toBe('[object Set]');
expect(toString(new WeakMap())).toBe('[object WeakMap]');
expect(toString(new WeakSet())).toBe('[object WeakSet]');
});
});
});

View File

@@ -0,0 +1,7 @@
/**
* To string any value.
*
* @param {any} value
* @returns {string}
*/
export const toString = (value: any): string => Object.prototype.toString.call(value);

View File

@@ -0,0 +1,183 @@
import { describe, expect, it } from 'vitest';
import { isArray, isObject, isRegExp, isDate, isError, isPromise, isMap, isSet, isWeakMap, isWeakSet } from './complex';
describe('complex', () => {
describe('isArray', () => {
it('true if the value is an array', () => {
expect(isArray([])).toBe(true);
expect(isArray([1, 2, 3])).toBe(true);
});
it('false if the value is not an array', () => {
expect(isArray('')).toBe(false);
expect(isArray(123)).toBe(false);
expect(isArray(true)).toBe(false);
expect(isArray(null)).toBe(false);
expect(isArray(undefined)).toBe(false);
expect(isArray({})).toBe(false);
expect(isArray(new Map())).toBe(false);
expect(isArray(new Set())).toBe(false);
});
});
describe('isObject', () => {
it('true if the value is an object', () => {
expect(isObject({})).toBe(true);
expect(isObject({ key: 'value' })).toBe(true);
});
it('false if the value is not an object', () => {
expect(isObject('')).toBe(false);
expect(isObject(123)).toBe(false);
expect(isObject(true)).toBe(false);
expect(isObject(null)).toBe(false);
expect(isObject(undefined)).toBe(false);
expect(isObject([])).toBe(false);
expect(isObject(new Map())).toBe(false);
expect(isObject(new Set())).toBe(false);
});
});
describe('isRegExp', () => {
it('true if the value is a regexp', () => {
expect(isRegExp(/test/)).toBe(true);
expect(isRegExp(new RegExp('test'))).toBe(true);
});
it('false if the value is not a regexp', () => {
expect(isRegExp('')).toBe(false);
expect(isRegExp(123)).toBe(false);
expect(isRegExp(true)).toBe(false);
expect(isRegExp(null)).toBe(false);
expect(isRegExp(undefined)).toBe(false);
expect(isRegExp([])).toBe(false);
expect(isRegExp({})).toBe(false);
expect(isRegExp(new Map())).toBe(false);
expect(isRegExp(new Set())).toBe(false);
});
});
describe('isDate', () => {
it('true if the value is a date', () => {
expect(isDate(new Date())).toBe(true);
});
it('false if the value is not a date', () => {
expect(isDate('')).toBe(false);
expect(isDate(123)).toBe(false);
expect(isDate(true)).toBe(false);
expect(isDate(null)).toBe(false);
expect(isDate(undefined)).toBe(false);
expect(isDate([])).toBe(false);
expect(isDate({})).toBe(false);
expect(isDate(new Map())).toBe(false);
expect(isDate(new Set())).toBe(false);
});
});
describe('isError', () => {
it('true if the value is an error', () => {
expect(isError(new Error())).toBe(true);
});
it('false if the value is not an error', () => {
expect(isError('')).toBe(false);
expect(isError(123)).toBe(false);
expect(isError(true)).toBe(false);
expect(isError(null)).toBe(false);
expect(isError(undefined)).toBe(false);
expect(isError([])).toBe(false);
expect(isError({})).toBe(false);
expect(isError(new Map())).toBe(false);
expect(isError(new Set())).toBe(false);
});
});
describe('isPromise', () => {
it('true if the value is a promise', () => {
expect(isPromise(new Promise(() => {}))).toBe(true);
});
it('false if the value is not a promise', () => {
expect(isPromise('')).toBe(false);
expect(isPromise(123)).toBe(false);
expect(isPromise(true)).toBe(false);
expect(isPromise(null)).toBe(false);
expect(isPromise(undefined)).toBe(false);
expect(isPromise([])).toBe(false);
expect(isPromise({})).toBe(false);
expect(isPromise(new Map())).toBe(false);
expect(isPromise(new Set())).toBe(false);
});
});
describe('isMap', () => {
it('true if the value is a map', () => {
expect(isMap(new Map())).toBe(true);
});
it('false if the value is not a map', () => {
expect(isMap('')).toBe(false);
expect(isMap(123)).toBe(false);
expect(isMap(true)).toBe(false);
expect(isMap(null)).toBe(false);
expect(isMap(undefined)).toBe(false);
expect(isMap([])).toBe(false);
expect(isMap({})).toBe(false);
expect(isMap(new Set())).toBe(false);
});
});
describe('isSet', () => {
it('true if the value is a set', () => {
expect(isSet(new Set())).toBe(true);
});
it('false if the value is not a set', () => {
expect(isSet('')).toBe(false);
expect(isSet(123)).toBe(false);
expect(isSet(true)).toBe(false);
expect(isSet(null)).toBe(false);
expect(isSet(undefined)).toBe(false);
expect(isSet([])).toBe(false);
expect(isSet({})).toBe(false);
expect(isSet(new Map())).toBe(false);
});
});
describe('isWeakMap', () => {
it('true if the value is a weakmap', () => {
expect(isWeakMap(new WeakMap())).toBe(true);
});
it('false if the value is not a weakmap', () => {
expect(isWeakMap('')).toBe(false);
expect(isWeakMap(123)).toBe(false);
expect(isWeakMap(true)).toBe(false);
expect(isWeakMap(null)).toBe(false);
expect(isWeakMap(undefined)).toBe(false);
expect(isWeakMap([])).toBe(false);
expect(isWeakMap({})).toBe(false);
expect(isWeakMap(new Map())).toBe(false);
expect(isWeakMap(new Set())).toBe(false);
});
});
describe('isWeakSet', () => {
it('true if the value is a weakset', () => {
expect(isWeakSet(new WeakSet())).toBe(true);
});
it('false if the value is not a weakset', () => {
expect(isWeakSet('')).toBe(false);
expect(isWeakSet(123)).toBe(false);
expect(isWeakSet(true)).toBe(false);
expect(isWeakSet(null)).toBe(false);
expect(isWeakSet(undefined)).toBe(false);
expect(isWeakSet([])).toBe(false);
expect(isWeakSet({})).toBe(false);
expect(isWeakSet(new Map())).toBe(false);
expect(isWeakSet(new Set())).toBe(false);
});
});
});

View File

@@ -0,0 +1,81 @@
import { toString } from '.';
/**
* Check if a value is an array.
*
* @param {any} value
* @returns {value is any[]}
*/
export const isArray = (value: any): value is any[] => Array.isArray(value);
/**
* Check if a value is an object.
*
* @param {any} value
* @returns {value is object}
*/
export const isObject = (value: any): value is object => toString(value) === '[object Object]';
/**
* Check if a value is a regexp.
*
* @param {any} value
* @returns {value is RegExp}
*/
export const isRegExp = (value: any): value is RegExp => toString(value) === '[object RegExp]';
/**
* Check if a value is a date.
*
* @param {any} value
* @returns {value is Date}
*/
export const isDate = (value: any): value is Date => toString(value) === '[object Date]';
/**
* Check if a value is an error.
*
* @param {any} value
* @returns {value is Error}
*/
export const isError = (value: any): value is Error => toString(value) === '[object Error]';
/**
* Check if a value is a promise.
*
* @param {any} value
* @returns {value is Promise<any>}
*/
export const isPromise = (value: any): value is Promise<any> => toString(value) === '[object Promise]';
/**
* Check if a value is a map.
*
* @param {any} value
* @returns {value is Map<any, any>}
*/
export const isMap = (value: any): value is Map<any, any> => toString(value) === '[object Map]';
/**
* Check if a value is a set.
*
* @param {any} value
* @returns {value is Set<any>}
*/
export const isSet = (value: any): value is Set<any> => toString(value) === '[object Set]';
/**
* Check if a value is a weakmap.
*
* @param {any} value
* @returns {value is WeakMap<object, any>}
*/
export const isWeakMap = (value: any): value is WeakMap<object, any> => toString(value) === '[object WeakMap]';
/**
* Check if a value is a weakset.
*
* @param {any} value
* @returns {value is WeakSet<object>}
*/
export const isWeakSet = (value: any): value is WeakSet<object> => toString(value) === '[object WeakSet]';

View File

@@ -0,0 +1,3 @@
export * from './casts';
export * from './primitives';
export * from './complex';

View File

@@ -0,0 +1,101 @@
import { describe, expect, it } from 'vitest';
import { isBoolean, isFunction, isNumber, isBigInt, isString, isSymbol, isUndefined, isNull } from './primitives';
describe('primitives', () => {
describe('isBoolean', () => {
it('true if the value is a boolean', () => {
expect(isBoolean(true)).toBe(true);
expect(isBoolean(false)).toBe(true);
});
it('false if the value is not a boolean', () => {
expect(isBoolean(0)).toBe(false);
expect(isBoolean('true')).toBe(false);
expect(isBoolean(null)).toBe(false);
});
});
describe('isFunction', () => {
it('true if the value is a function', () => {
expect(isFunction(() => {})).toBe(true);
expect(isFunction(function() {})).toBe(true);
});
it('false if the value is not a function', () => {
expect(isFunction(123)).toBe(false);
expect(isFunction('function')).toBe(false);
expect(isFunction(null)).toBe(false);
});
});
describe('isNumber', () => {
it('true if the value is a number', () => {
expect(isNumber(123)).toBe(true);
expect(isNumber(3.14)).toBe(true);
});
it('false if the value is not a number', () => {
expect(isNumber('123')).toBe(false);
expect(isNumber(null)).toBe(false);
});
});
describe('isBigInt', () => {
it('true if the value is a bigint', () => {
expect(isBigInt(BigInt(123))).toBe(true);
});
it('false if the value is not a bigint', () => {
expect(isBigInt(123)).toBe(false);
expect(isBigInt('123')).toBe(false);
expect(isBigInt(null)).toBe(false);
});
});
describe('isString', () => {
it('true if the value is a string', () => {
expect(isString('hello')).toBe(true);
});
it('false if the value is not a string', () => {
expect(isString(123)).toBe(false);
expect(isString(null)).toBe(false);
});
});
describe('isSymbol', () => {
it('true if the value is a symbol', () => {
expect(isSymbol(Symbol())).toBe(true);
});
it('false if the value is not a symbol', () => {
expect(isSymbol(123)).toBe(false);
expect(isSymbol('symbol')).toBe(false);
expect(isSymbol(null)).toBe(false);
});
});
describe('isUndefined', () => {
it('true if the value is undefined', () => {
expect(isUndefined(undefined)).toBe(true);
});
it('false if the value is not undefined', () => {
expect(isUndefined(null)).toBe(false);
expect(isUndefined(123)).toBe(false);
expect(isUndefined('undefined')).toBe(false);
});
});
describe('isNull', () => {
it('true if the value is null', () => {
expect(isNull(null)).toBe(true);
});
it('false if the value is not null', () => {
expect(isNull(undefined)).toBe(false);
expect(isNull(123)).toBe(false);
expect(isNull('null')).toBe(false);
});
});
});

View File

@@ -0,0 +1,65 @@
import { toString } from '.';
/**
* Check if a value is a boolean.
*
* @param {any} value
* @returns {value is boolean}
*/
export const isBoolean = (value: any): value is boolean => typeof value === 'boolean';
/**
* Check if a value is a function.
*
* @param {any} value
* @returns {value is Function}
*/
export const isFunction = <T extends Function>(value: any): value is T => typeof value === 'function';
/**
* Check if a value is a number.
*
* @param {any} value
* @returns {value is number}
*/
export const isNumber = (value: any): value is number => typeof value === 'number';
/**
* Check if a value is a bigint.
*
* @param {any} value
* @returns {value is bigint}
*/
export const isBigInt = (value: any): value is bigint => typeof value === 'bigint';
/**
* Check if a value is a string.
*
* @param {any} value
* @returns {value is string}
*/
export const isString = (value: any): value is string => typeof value === 'string';
/**
* Check if a value is a symbol.
*
* @param {any} value
* @returns {value is symbol}
*/
export const isSymbol = (value: any): value is symbol => typeof value === 'symbol';
/**
* Check if a value is a undefined.
*
* @param {any} value
* @returns {value is undefined}
*/
export const isUndefined = (value: any): value is undefined => toString(value) === '[object Undefined]';
/**
* Check if a value is a null.
*
* @param {any} value
* @returns {value is null}
*/
export const isNull = (value: any): value is null => toString(value) === '[object Null]';

View File

@@ -0,0 +1 @@
export * from './string';

View File

@@ -0,0 +1,43 @@
import { describe, expectTypeOf, it } from 'vitest';
import type { HasSpaces, Trim } from './string';
describe('string', () => {
describe('Trim', () => {
it('remove leading and trailing spaces from a string', () => {
type actual = Trim<' hello '>;
type expected = 'hello';
expectTypeOf<actual>().toEqualTypeOf<expected>();
});
it('remove only leading spaces from a string', () => {
type actual = Trim<' hello'>;
type expected = 'hello';
expectTypeOf<actual>().toEqualTypeOf<expected>();
});
it('remove only trailing spaces from a string', () => {
type actual = Trim<'hello '>;
type expected = 'hello';
expectTypeOf<actual>().toEqualTypeOf<expected>();
});
});
describe('HasSpaces', () => {
it('check if a string has spaces', () => {
type actual = HasSpaces<'hello world'>;
type expected = true;
expectTypeOf<actual>().toEqualTypeOf<expected>();
});
it('check if a string has no spaces', () => {
type actual = HasSpaces<'helloworld'>;
type expected = false;
expectTypeOf<actual>().toEqualTypeOf<expected>();
});
});
});

View File

@@ -0,0 +1,9 @@
/**
* Trim leading and trailing whitespace from `S`
*/
export type Trim<S extends string> = S extends ` ${infer R}` ? Trim<R> : S extends `${infer L} ` ? Trim<L> : S;
/**
* Check if `S` has any spaces
*/
export type HasSpaces<S extends string> = S extends `${string} ${string}` ? true : false;

View File

@@ -31,7 +31,7 @@ pnpm install -D @robonen/tsconfig
"resolveJsonModule": true, // разрешить импортировать файлы JSON
"moduleDetection": "force", // заставляет TypeScript рассматривать все файлы как модули. Это помогает избежать ошибок cannot redeclare block-scoped variable»
"isolatedModules": true, // орабатывать каждый файл, как отдельный изолированный модуль
"removeComments": true, // удалять комментарии из исходного кода
"removeComments": false, // удалять комментарии из исходного кода
"verbatimModuleSyntax": true, // сохранять синтаксис модулей в исходном коде (важно при импорте типов)
"useDefineForClassFields": true, // использование классов стандарта TC39, а не TypeScript
"strict": true, // включить все строгие проверки (noImplicitAny, noImplicitThis, alwaysStrict, strictNullChecks, strictFunctionTypes, strictPropertyInitialization)

View File

@@ -17,7 +17,7 @@
"resolveJsonModule": true,
"moduleDetection": "force",
"isolatedModules": true,
"removeComments": true,
"removeComments": false,
"verbatimModuleSyntax": true,
"useDefineForClassFields": true,