A collection of useful modules with a no-dependency, clean, tested and lightweight code philosophy.
npm i @materya/carbon
Module dedicated to explore and manipulate filesystem related operations.
Search a given file name
in the current directory cwd
and up the tree until it reaches the top. Will throw if no matching file is found.
import * as carbon from '@materya/carbon'
const rcFileName = '.rc.json'
const rcFilePath = carbon.find.up(process.cwd(), rcFileName)
Crawl from directory
, listing all files sorted until depth
is reached.
import * as carbon from '@materya/carbon'
const list = carbon.crawl.list(process.cwd(), 2)
/*
> list
[
'filename1.ext',
'filename2.ext',
'dir1/filename1.ext',
'dir1/filename2.ext',
'dir1/dir2/filename1.ext',
]
*/
Crawl from directory
, sorting out files and directories in a tree-like object until depth
is reached.
import * as carbon from '@materya/carbon'
const tree = carbon.crawl.tree(process.cwd(), 2)
/*
> tree
{
files: ['filename.ext', 'filename.ext'],
directories: {
somedir: {
files: ['filename.ext', 'filename.ext'],
directories: {},
},
...
},
}
*/
Crawl from directory
, triggering action(name: string, path: string)
callback on each file found.
import * as carbon from '@materya/carbon'
const action: FileActionCallback = (name, path) => { /* do something */ }
carbon.crawl.trigger(process.cwd(), action)
Module dedicated to manage process.env
and access environment variables.
Get a given name
env variable. Will throw unless a defaultValue
is provided.
import * as carbon from '@materya/carbon'
const myEnv = carbon.env.get('MY_ENV', 42)
Various utility methods to make life easier.
Based on numerous threads and propositions across the internet (mainly SO), this method aims to identify with the best accuracy and the least complexity if a given value is an "object" (ie. an associative array).
See all tests at ./test/tools/object.test.ts
import { tools } from '@materya/carbon'
tools.object.isObject({}) // true
tools.object.isObject([]) // false
tools.object.isObject(() => null) // false
tools.object.isObject((new Date())) // false
// ...
Deep merge together nested arrays or maps.
NOTE: The later sources keys take precedence over the previous same ones if a "conflict" arise.
import * as carbon from '@materya/carbon'
const map1 = {
animals: {
cats: {
types: ['lion', 'tiger'],
props: {
run: true,
eat: true,
die: true,
},
},
cars: ['dodge', 'ford'],
},
}
const map2 = {
animals: {
cats: {
types: ['cheetah'],
props: {
fly: false,
die: false, // it is well known that cats have 9 lives and don't die.
},
},
},
cars: ['audi', 'bmw'],
}
const merged = carbon.tools.merge(map1, map2)
/*
> merged
{
animals: {
cats: {
types: ['lion', 'tiger', 'cheetah'],
props: {
run: true,
eat: true,
die: false,
fly: false,
},
},
cars: ['dodge', 'ford', 'audi', 'bmw'],
},
}
*/
Extract specific keys from a given object and return a new one with only those keys.
import * as carbon from '@materya/carbon'
const obj = {
a: 'foo',
b: 42,
c: 'bar',
d: { obj: 'foobar' },
}
const partialObj = carbon.tools.pick(obj)('b', 'd')
/*
> partialObj
{
b: 42,
d: { obj: 'foobar' },
}
*/
Carbon provides also a set of utility types.
Since the standard type object
is hard to use and error prone, this general type aims to replace it.
See microsoft/TypeScript#21732 for more details.
type ObjectLiteral = Record<keyof any, unknown>
Workaround to fix an index signature issue with interfaces.
See microsoft/TypeScript#15300 for more details.
interface Todo {
title: string;
description?: string;
completed: boolean;
}
type TypeWithGenericObject<T extends Record<string | number | symbol, unknown>> = T
let test1: TypeWithGenericObject<Todo> // Throw a ts(2344) error
let test2: TypeWithGenericObject<CastIndexSignature<Todo>> // Works
Traverse a Type
extending an object to switch all the props as optionals, recursively.
interface Todo {
title: string;
description: string;
metadata: {
foo: {
bar: {
ber: string;
};
};
};
completed: boolean;
}
type DeepPartialTodo: DeepPartial<Todo>
// type DeepPartialTodo = {
// title?: string;
// description?: string;
// metadata?: {
// foo?: {
// bar?: {
// ber?: string;
// };
// };
// };
Traverse a Type
extending an object to switch all the props as required, recursively.
interface Todo {
title?: string;
description?: string;
metadata?: {
foo?: {
bar?: {
ber?: string;
};
};
};
completed?: boolean;
}
type DeepRequiredTodo: DeepRequired<Todo>
// type DeepRequiredTodo = {
// title: string;
// description: string;
// metadata: {
// foo: {
// bar: {
// ber: string;
// };
// };
// };
Type alias that makes a given standard Type
like string
unique by its Alias
name.
Similar and inspired by Flow https://flow.org/en/docs/types/opaque-types/
type UniqueString = Opaque<'UniqueString', string>
const uniqueString: UniqueString = 'foobar'
const nonUniqueString = 'foobar'
const f = (arg: UniqueString): UniqueString => arg
f(nonUniqueString) // ts(2345) error: Argument of type 'string' is not assignable to parameter of type 'UniqueString'
f(uniqueString) // Valid
const f2 = (arg: string): string => arg
f2(nonUniqueString) // Valid
f2(uniqueString) // Valid, still a string
Constructs a set of properties type by extracting all the optional keys from Type
.
type Todo = {
title: string;
description?: string;
completed: boolean;
}
type TodoOptionalProps = OptionalProps<Todo>
// type TodoOptionalProps = "description"
Constructs a set of properties type by extracting all the required keys from Type
.
type Todo = {
title: string;
description?: string;
completed: boolean;
}
type TodoRequiredProps = RequiredProps<Todo>
// type TodoRequiredProps = "title" | "completed"
Constructs a type by picking all properties from Type
and then switching Keys
as optionals.
type Todo = {
title: string;
description?: string;
completed: boolean;
createdAt: number;
assignee: string;
}
type PartialTodo = SelectivePartial<Todo, 'createdAt' | 'assignee'>
// type PartialTodo = {
// title: string; // Stays required
// description?: string;
// completed: boolean; // Stays required
// createdAt?: number;
// assignee?: string;
// }
Constructs a type by picking all properties from Type
and then switching Keys
as required.
type Todo = {
title: string;
description?: string;
completed: boolean;
createdAt: number;
assignee?: string;
reviewer?: string;
}
type RequiredTodo = SelectiveRequired<Todo, 'assignee' | 'reviewer'>
// type RequiredTodo = {
// title: string;
// description?: string; // Stays optional
// completed: boolean;
// createdAt: number;
// assignee: string;
// reviewer: string;
// }
Constructs a new type as a valid subset of a given union Type
by picking up all given Values
from it.
type FooBarBaz =
| 'foo'
| 'bar'
| 'baz'
// Valid Type
type FooBar = UnionPick<FooBarBaz, 'foo' | 'bar'>
// Invalid Type
type FooBer = UnionPick<FooBarBaz, 'foo' | 'ber'>
// ^^^^^^^^^^^^^
// Type '"foo" | "ber"' does not satisfy the constraint 'FooBarBaz'.
// Type '"ber"' is not assignable to type 'FooBarBaz'.ts(2344)