Skip to content

Instantly share code, notes, and snippets.

View temoncher's full-sized avatar
🐢

Artem Baranov temoncher

🐢
View GitHub Profile
@temoncher
temoncher / strongly-typed-fsm.ts
Last active March 5, 2022 07:23
Builder pattern for strongly typed Finite State Machines in TypeScript
type State = string;
type Message = string;
type StatesList = readonly State[];
type MessagesConfig = Record<State, Record<Message, State>>;
type OneOf<S extends StatesList> = S[number];
type Send<
SBConfig extends StateBuilderConfig<StatesList, State, State>,
@temoncher
temoncher / number-to-alphabet-code.ts
Created September 30, 2021 19:19
Number to alphabet code (53 => ZZA)
const alphabetLength = 26;
const numberOfDigits = 9;
const toStr = (num: number): string => {
return Number(num + numberOfDigits).toString(alphabetLength + numberOfDigits + 1).toLocaleUpperCase();
}
const stringify = (num: number): string | undefined => {
if (num <= 0) {
return undefined;
@temoncher
temoncher / useTypedParams.tsx
Created July 28, 2022 09:43
Search params typed with `zod` lib
import { useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';
import { z } from 'zod';
export const useTypedParams = <T extends z.SomeZodObject>(zodSchema: T) => {
type TParams = z.infer<T>;
const [urlSearchParams, setUrlSearchParams] = useSearchParams();
const searchParams = useMemo<TParams>(() => {
@temoncher
temoncher / PageSchema.ts
Last active July 28, 2022 09:49
`zod` URL search params `page` schema
import { z } from 'zod';
export const PageSchema = z
.string()
.refine((s) => {
const n = Number(s);
return Number.isFinite(n) && !Number.isNaN(n);
})
.transform(Number);
@temoncher
temoncher / PermissionsContext.tsx
Created July 28, 2022 09:49
`usePermissions` hook
import React, { useState, useEffect, useCallback, useContext } from 'react';
import { useAuth } from '../AuthContext';
const allPermissions = [
'posts:write',
'posts:read',
'comments:write',
'comments:read',
] as const;
@temoncher
temoncher / BoundedList.ts
Created August 3, 2022 12:34
TypeScript meta brands for typesafe list access
type InBoundsOf<L extends string, N extends number = number> = N & { __inBoundsOf: L };
type OutsideOfBoundsOf<L extends string, N extends number = number> = N & { __outsideOfBounds: L };
type BoundedNumberOf<L extends string, N extends number = number> = InBoundsOf<L, N> | OutsideOfBoundsOf<L, N>
class List<T, L extends string> {
constructor(public readonly arr: T[]) { }
at(pos: InBoundsOf<L>): T;
at(pos: OutsideOfBoundsOf<L>): undefined;
@temoncher
temoncher / isEnum.ts
Last active September 2, 2022 10:58
`isEnum` typeguard
export const isEnum = <T extends string | number, TEnumValue extends string>(enumVariable: { [key in T]: TEnumValue }) => {
const enumValues = Object.values(enumVariable);
return (value: unknown): value is TEnumValue => (typeof value === 'string' || typeof value === 'number') && enumValues.includes(value);
};
// with number indexes not allowed
export const isEnum = <T extends string, TEnumValue extends string>(enumVariable: { [key in T]: TEnumValue }) => {
const enumValues = Object.values(enumVariable);
@temoncher
temoncher / endsWith.ts
Created September 5, 2022 10:16
`endsWith` typeguard
type EndsWithResult<S extends string, E extends string> = S extends `${string}${E}` ? S : `${S}${E}`
const endsWith = <E extends string>(end: E) => <S extends string>(str: S): str is EndsWithResult<S, E> => {
return str.slice(-end.length) === end;
}
const str = 'something' as `some${string}`;
const qqq = endsWith('foo')(str);
if (qqq) {
str;
@temoncher
temoncher / updatePackageJson.js
Created September 7, 2022 12:55
Update `package.json` package versions with `package-lock.json` versions
const fs = require('fs');
const { mapValues } = require('lodash');
const lock = require('./package-lock.json');
const pack = require('./package.json');
const newDependencies = mapValues(
pack.dependencies,
(depVersion, depName) => lock.dependencies[depName].version
);
const newDevDepencies = mapValues(
@temoncher
temoncher / stringIncludes.ts
Last active October 27, 2022 11:10
Typed String.prototype.includes
type FilterUnion<T extends string, Keys extends string> = {
[Key in Keys extends infer R ? (R extends `${string}${T}${string}` ? R : never) : never]: string;
} extends infer O
? keyof O
: never;
export const stringIncludes = <T extends string>(text: T) => <S extends string>(
str: S
): str is FilterUnion<T, S> => str.includes(text);