Introduction
"Haetae" represents its packages, haetae
and @haetae/*
.
API
Type notations
Types are annotated by typescript syntax.
For example, trailing ?
means an optional field.
PromiseOr
Some APIs depend on a utility type named PromiseOr
.
type PromiseOr<T> = Promise<T> | T
haetae
and @haetae/*
does NOT export PromiseOr
, but you'd better know this.
Rec
Some APIs depend on a utility type named Rec
.
type Rec = Record<string, unknown>
haetae
and @haetae/*
does NOT export Rec
, but you'd better know this.
Memoization
Memoization (opens in a new tab) is a technique returning the cached result.
Some functions (e.g. getConfig
, getStore
) are memoized.
The cache only exists in the same process's memory and is cleared when the process is terminated.
Linked by label
Any functions linked to from here by Memoized label satisfy the principles below.
Cache hit and clear
To clear the memoization cache, you can call <function>.clear()
.
For example, core.getConfig
is a memoized function, and getConfig.clear()
would clear its cache.
import { getConfig } from '@haetae/core'
// `getConfig` is executed.
const config1 = await getConfig({ filename: '/foo/haetae.config.js' })
// `getConfig` is executed. No memoization cache hit, due to the different argument.
const config2 = await getConfig({ filename: '/bar/haetae.config.js' })
// Cache hit from the 1st call result, thanks to the same argument.
// `getConfig` is not executed. Just returned from the memoization cache.
const config3 = await getConfig({ filename: '/foo/haetae.config.js' })
// Cache hit from the 2nd call result, thanks to the same argument.
const config4 = await getConfig({ filename: '/bar/haetae.config.js' })
// Clear the memoization cache entirely
getConfig.clear()
// `getConfig` is freshly executed without cache.
// A new cache is created from now on again.
const config5 = await getConfig({ filename: '/foo/haetae.config.js' })
Cache by shallow copy
The memoization cache is based on shallow copy.
// `config1` and `config2` would have same memory address.
const config1 = await getConfig()
const config2 = await getConfig()
config1.foo = 'bar'
// `config2` is also modified when `config1` is modified
console.log(config2.foo) // 'bar'
const config3 = await getConfig()
console.log(config3.foo) // 'bar'
If you want to avoid the side effect, you can clear the cache before calling getConfig
.
Or deep copy techniques like immer (opens in a new tab) can be a good solution.
Path Principles
Haetae has a few design principles for file and directory paths.
Linked by label
Any functions linked to from here by Path Principles label satisfy the principles below.
1. Absolute Return Value
Returned file or directory path is always absolute path (NOT relative).
This is true even when the path is not a directly returned entity, like even when it's an element of an array or object field.
For instance, glob
of @haetae/utils
and changedFiles
of @haetae/git
are such functions.
import { utils } from 'haetae'
const files = await utils.glob(['**/*.test.ts', '**/*.test.tsx'])
// ['/path/to/foo.test.ts', '/path/to/bar.test.tsx', '/path/to/baz.test.tsx']
Arguments or options, which are not return value, don't have to be/contain an absolute path.
In fact, for arguments or options, relative paths would probably be more suitable for the majority of cases.
2. /
As Delimiter
Haetae only officially uses/supports /
as a delimiter for a path.
/
is traditionally used in POSIX (e.g. Linux, macOS).
However, /
works well on Windows in Node.js (and other languages or runtimes as well).
Thus, Haetae does NOT officially use/support Windows legacy \
delimiter.
This is a better decision for cross-platform design as well as convenience of usage and development.
-
Return Value: Delimiter is always
/
. This is true even when the path is not a returned entity itself but a part of it, like an element of an array or a field of object.For example,
import { core } from 'haetae' const config = await core.configure({ storeFile: '..\..\.haetae\store.json', /* ... */ }) // Relative path is transformed to an absolute path. // `\` is transformed to `/`. console.log(config.storeFile) // => '/path/to/.haetae/store.json'
-
Function Argument or Option: It MAY PROBABLY work, while not guaranteed officially, even when an (part of) argument or an option's path delimiter is
\
on Windows. But using/
is always officially recommended.
pkg
haetae
and @haetae/*
have this export.
pkg
contains the package's meta information, name
and version
.
For example, let's assume the package version is 1.2.3-beta.4
.
Then the value would be like this.
const { pkg } = require('haetae' /* or '@haetae/<package>' */)
pkg.name // 'haetae' or '@haetae/<package>'
pkg.version.value // '1.2.3-beta.4'
pkg.version.major // 1 // Integer
pkg.version.minor // 2 // Integer
pkg.version.patch // 3 // Integer
pkg.version.prerelease // ['beta', 4] // 'beta' is string, 4 is integer
pkg.version.untilMinor // '1.2'
pkg.version.untilPatch // '1.2.3'
pkg.version.*
could be a good choice for env
.
import { core, pkg } from 'haetae'
export default core.configure({
// Other options are omitted for brevity.
commands: {
myAwesomeCommand: {
env: {
// Different OS are to be treated as different environments.
os: process.platform
// Different major versions of `haetae` as well.
haetae: pkg.version.major
},
run: () => { /* ... */ }
}
},
})