Skip to content

Instantly share code, notes, and snippets.

View cefn's full-sized avatar

Cefn Hoile cefn

View GitHub Profile
@cefn
cefn / reconcile_promise_lookup.ts
Created December 20, 2023 14:44
Create a synchronous structure from a lookup of promises
/** Stricter keyof */
export type RecordKey<T> = T extends Partial<infer R> ? (R extends Record<infer K, unknown> ? K : never) : never;
/** Stricter type for members of Object.entries(t) */
export type RecordEntry<T> = { [K in RecordKey<T>]: [K, Required<T>[K]] }[RecordKey<T>];
/** Type-narrowed equivalent of Object.entries */
export function entries<T extends Readonly<NonNullable<{}>>>(obj: T) {
return Object.entries(obj) as RecordEntry<T>[];
}
@cefn
cefn / tuples.ts
Created September 6, 2023 13:44
Type-safe tuple composition from literal numbers
/** The union of the types of members of array `Arr` */
export type MemberOf<Arr extends readonly unknown[]> = Arr[number];
// from https://stackoverflow.com/a/65914848/2257198
type Tuple<T, N extends number, A extends unknown[] = []> = A extends { length: N } ? A : Tuple<T, N, [...A, T]>;
export function composeTuple<Item, Length extends number, LengthCast = [...Item[]]>(item: Item, length: Length) {
return Array.from({ length }).map(() => item) as LengthCast;
}
@cefn
cefn / mapGenerator.ts
Last active July 10, 2023 00:24
Mapping a generator by decorated yielded values.
export function decorateSequence<
G extends Generator<Yielded>,
Yielded,
Decorated
>(
generator: G,
decorate: (yielded: Yielded) => Decorated
): Generator<Decorated, GReturned<G>, GNexted<G>> {
function mapResult(
result: IteratorResult<Yielded, GReturned<G>>
@cefn
cefn / story.ts
Created November 9, 2022 02:05
Interactive Story sketch
type Passage = string;
async function tell(passage: Passage): Promise<void> {}
async function choose<Choice extends string>(
passage: Passage,
choices: Record<Choice, Passage>
): Promise<Choice> {
return Object.keys(choices)[0] as Choice;
}
@cefn
cefn / returned.ts
Created October 16, 2022 00:21
Infer TReturn type from Generator or AsyncGenerator
export type Returned<
G extends Generator<any, any, any> | AsyncGenerator<any, any, any>
> = G extends Generator<any, infer TReturn, any>
? TReturn
: G extends AsyncGenerator<any, infer AsycTReturn, any>
? AsycTReturn
: never;
@cefn
cefn / yielded.ts
Created October 16, 2022 00:20
Inference of T yielded by Generator or AsyncGenerator
export type Yielded<
G extends Generator<any, any, any> | AsyncGenerator<any, any, any>
> = G extends Generator<infer T, any, any>
? T
: G extends AsyncGenerator<infer AsyncT, any, any>
? AsyncT
: never;
@cefn
cefn / indexOf.ts
Created September 29, 2022 23:39
Typescript - derive index of tuple from tuple type
type IndexOf<T extends {length: number}> = Exclude<Partial<T>["length"], T['length']> & number;
@cefn
cefn / waitableJestFn.ts
Created August 19, 2022 01:35
Create a jest mock function which is awaitable
function waitableJestFn<Result, Args extends any[]>(
implementation?: (...args: Args) => Result,
) {
const mock = jest.fn<Result, Args>(implementation);
const asyncMethods = {
waitForCallback: (count = 1) =>
new Promise<Args>((resolve) => {
mock.mockImplementation(((...args) => {
count--;
if (count === 0) resolve(args);
@cefn
cefn / gist:c4b3ab064c55dd8c05b73092036bb1e9
Created April 26, 2022 13:28
Possible follow implementation to map named children to named VM properties
import { computed, ComputedRef } from 'vue';
export function followDescendantsByName<
ReactiveParent,
ChildKeys extends ReadonlyArray<keyof ReactiveParent>,
>(parentSelector: () => ReactiveParent, childKeys: ChildKeys) {
return Object.fromEntries(
childKeys.map((childKey) => {
return [childKey, computed(() => parentSelector()[childKey])];
}),
@cefn
cefn / config.ts
Created January 29, 2022 09:04
Env Vars with enforcement and typing
import * as dotenv from 'dotenv';
dotenv.config();
const VARS = {
API_TOKEN: 'an API token',
API_URL: 'a URL for an endpoint',
} as const;
for (const [varname, description] of Object.entries(VARS)) {