Skip to content

Commit 36ff882

Browse files
committed
feat: 🎸 init @sirutils/core
1 parent d931bd8 commit 36ff882

File tree

17 files changed

+681
-336
lines changed

17 files changed

+681
-336
lines changed

‎biome.jsonc

+334-334
Large diffs are not rendered by default.

‎bun.lockb

1.07 KB
Binary file not shown.

‎packages/core/moon.yml

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
language: "typescript"
2+
type: "library"
3+
platform: "bun"
4+
5+
tasks:
6+
build:
7+
command: "bun x builder build src/index.ts -a"
8+
deps:
9+
- "core:clean"
10+
- "builder:build"
11+
inputs:
12+
- "src/**/*"
13+
- "types/**/*"
14+
outputs:
15+
- "dist/**"
16+
watch:
17+
command: "bun x builder build src/index.ts -a -w"
18+
deps:
19+
- "core:clean"
20+
- "builder:build"
21+
local: true
22+
inputs:
23+
- "src/**/*"
24+
- "types/**/*"

‎packages/core/package.json

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "@sirutils/core",
3+
"version": "0.0.1",
4+
"type": "module",
5+
6+
"main": "dist/index.js",
7+
"module": "dist/index.js",
8+
"files": ["dist"],
9+
"types": "dist/index.d.ts",
10+
11+
"devDependencies": {
12+
"@sirutils/builder": "workspace:*"
13+
},
14+
"dependencies": {
15+
"neverthrow": "^6.2.1"
16+
}
17+
}
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { Result } from 'neverthrow'
2+
3+
import type { ProjectError } from '../result/error'
4+
import type { ProjectMessage } from '../result/message'
5+
import type { CoreTags } from '../tag'
6+
7+
declare global {
8+
// biome-ignore lint/style/noNamespace: Redundant
9+
namespace Sirutils {
10+
type ProjectErrorType = ProjectError
11+
type ProjectMessageType = ProjectMessage
12+
13+
// ------------ Errors ------------
14+
15+
// biome-ignore lint/suspicious/noEmptyInterface: for future overriding
16+
interface CustomErrors {}
17+
18+
// use this instead of CustomErrors. CustomErrors is for overriding
19+
interface Error extends Sirutils.CustomErrors {
20+
core: CoreTags
21+
}
22+
23+
// ------------ Messages ------------
24+
25+
// biome-ignore lint/suspicious/noEmptyInterface: for future overriding
26+
interface CustomMessages {}
27+
28+
// use this instead of CustomMessages. CustomMessages is for overriding
29+
interface Message extends CustomMessages {}
30+
31+
type MessageResult = Result<Sirutils.ProjectMessageType, Sirutils.ProjectErrorType>
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import './core.ts'

‎packages/core/src/index.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import './definitions'
2+
3+
export * from './tag'
4+
5+
export * from './result/error'
6+
export * from './result/message'
7+
8+
export * from './utils/env'
9+
export * from './utils/lazy'
10+
export * from './utils/common'
11+
12+
export * from 'neverthrow'

‎packages/core/src/internal/tag.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const TAG = '@sirutils/core' as const
2+
3+
export const createTag = <const T>(str: T) => `${TAG}.${str}` as TagMapper<T>
4+
5+
export type TagMapper<T> = T extends string ? `${typeof TAG}.${T}` : null

‎packages/core/src/result/error.ts

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { type Err, Result, ResultAsync, err, ok } from 'neverthrow'
2+
3+
import { coreTags } from '../tag'
4+
import type { BlobType, Promisify } from '../utils/common'
5+
6+
export class ProjectError extends Error {
7+
constructor(
8+
public name: Sirutils.Error[keyof Sirutils.Error],
9+
public message: string,
10+
public cause: BlobType[] = []
11+
) {
12+
super()
13+
}
14+
15+
appendCause(additionalCause?: string) {
16+
if (additionalCause && this.cause[this.cause.length - 1] !== additionalCause) {
17+
this.cause.push(additionalCause)
18+
}
19+
20+
return this
21+
}
22+
23+
asResult(additionalCause?: string) {
24+
return err(this.appendCause(additionalCause))
25+
}
26+
27+
static create = (
28+
name: Sirutils.Error[keyof Sirutils.Error],
29+
message: string,
30+
cause?: BlobType
31+
) => {
32+
if (cause) {
33+
return new ProjectError(name, message, [cause])
34+
}
35+
36+
return new ProjectError(name, message)
37+
}
38+
}
39+
40+
export const unwrap = <T, E extends Sirutils.ProjectErrorType>(
41+
result: Result<T, E>,
42+
additionalCause?: string
43+
): T | never => {
44+
if (result.isErr()) {
45+
if (additionalCause) {
46+
result.error.appendCause(additionalCause)
47+
}
48+
49+
throw result.error
50+
}
51+
52+
return result.value
53+
}
54+
55+
export const group = <T, E extends Sirutils.ProjectErrorType>(
56+
body: () => T,
57+
additionalCause: string
58+
): Result<T, E> => {
59+
try {
60+
return ok(body())
61+
} catch (e) {
62+
if (e instanceof ProjectError) {
63+
return e.asResult(additionalCause) as unknown as Err<T, E>
64+
}
65+
66+
return ProjectError.create(
67+
coreTags.group,
68+
`${e}`,
69+
additionalCause
70+
).asResult() as unknown as Err<T, E>
71+
}
72+
}
73+
74+
export const groupAsync = async <T, E extends Sirutils.ProjectErrorType>(
75+
body: () => Promisify<T>,
76+
additionalCause: string
77+
): Promise<Result<T, E>> => {
78+
try {
79+
return ok(await body())
80+
} catch (e) {
81+
if (e instanceof ProjectError) {
82+
return e.asResult(additionalCause) as unknown as Err<T, E>
83+
}
84+
85+
return ProjectError.create(
86+
coreTags.groupAsync,
87+
`${e}`,
88+
additionalCause
89+
).asResult() as unknown as Err<T, E>
90+
}
91+
}
92+
93+
export const wrap = <A extends BlobType[], T, E extends Sirutils.ProjectErrorType>(
94+
body: (...args: A) => T,
95+
additionalCause: string
96+
) => {
97+
return Result.fromThrowable(
98+
body,
99+
e =>
100+
(e instanceof ProjectError
101+
? e.appendCause(additionalCause)
102+
: ProjectError.create(coreTags.wrap, `${e}`, additionalCause)) as E
103+
)
104+
}
105+
106+
export const wrapAsync = <A extends BlobType[], T, E extends Sirutils.ProjectErrorType>(
107+
body: (...args: A) => PromiseLike<T>,
108+
additionalCause: string
109+
) => {
110+
return (...args: A) =>
111+
ResultAsync.fromPromise<T, E>(
112+
Promise.resolve(body(...args)),
113+
e =>
114+
(e instanceof ProjectError
115+
? e.appendCause(additionalCause)
116+
: ProjectError.create(coreTags.wrapAsync, `${e}`, additionalCause)) as E
117+
)
118+
}
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { ok } from 'neverthrow'
2+
3+
import type { BlobType } from '../utils/common'
4+
5+
export class ProjectMessage {
6+
constructor(
7+
public name: Sirutils.Message[keyof Sirutils.Message],
8+
public message: string,
9+
public data?: BlobType
10+
11+
// biome-ignore lint/suspicious/noEmptyBlockStatements: <explanation>
12+
) {}
13+
14+
asResult() {
15+
return ok(this)
16+
}
17+
18+
static create(name: Sirutils.Message[keyof Sirutils.Message], message: string, data?: BlobType) {
19+
return new ProjectMessage(name, message, data)
20+
}
21+
}

‎packages/core/src/tag.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { createTag } from './internal/tag'
2+
3+
export const coreTags = {
4+
env: createTag('invalid-env'),
5+
lazy: createTag('lazy-unexpected'),
6+
7+
group: createTag('group-missused'),
8+
groupAsync: createTag('group-async-missused'),
9+
10+
wrap: createTag('wrap-missused'),
11+
wrapAsync: createTag('wrap-async-missused'),
12+
} as const
13+
14+
export type CoreTags = (typeof coreTags)[keyof typeof coreTags]

‎packages/core/src/utils/common.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { Lazy } from './lazy'
2+
3+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
4+
export type BlobType = any
5+
6+
// biome-ignore lint/complexity/noBannedTypes: <explanation>
7+
export type EmptyType = {}
8+
9+
export type Mutable<T> = {
10+
-readonly [K in keyof T]: Mutable<T[K]>
11+
}
12+
13+
export type Promisify<T> = T | PromiseLike<T> | Lazy<T>
14+
15+
export type Fn<T, U> = (...args: T[]) => U
16+
17+
export const isDev = !Bun.env.NODE_ENV || Bun.env.NODE_ENV === 'development'
18+
19+
export const deepFreeze = (object: BlobType) => {
20+
const propNames = Reflect.ownKeys(object)
21+
22+
for (const name of propNames) {
23+
const value = object[name]
24+
25+
if ((value && typeof value === 'object') || typeof value === 'function') {
26+
deepFreeze(value)
27+
}
28+
}
29+
30+
return Object.freeze(object)
31+
}

‎packages/core/src/utils/env.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ProjectError, unwrap, wrap } from '../result/error'
2+
import { coreTags } from '../tag'
3+
import type { BlobType, EmptyType } from './common'
4+
5+
export type ExtractEnvsCallback<T extends EmptyType> = (envList: BlobType) => T
6+
export type Target = 'bun' | 'node'
7+
8+
export const extractEnvs = wrap(
9+
<T extends EmptyType>(callback: ExtractEnvsCallback<T>, target: Target = 'bun') => {
10+
const selectedTarget: BlobType =
11+
target === 'bun' && typeof Bun !== 'undefined' ? Bun.env : process.env
12+
13+
const result = callback(selectedTarget)
14+
15+
for (const [key, value] of Object.entries(result)) {
16+
if (!value) {
17+
unwrap(ProjectError.create(coreTags.env, `ENV.${key} is not valid`).asResult())
18+
}
19+
}
20+
},
21+
coreTags.env
22+
)

‎packages/core/src/utils/lazy.ts

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { Fn, Promisify } from './common'
2+
3+
type RawExecutor<T> = (
4+
resolve: (value: T) => void,
5+
reject: (error: Sirutils.ProjectErrorType) => void
6+
) => void
7+
8+
export class Lazy<T> implements PromiseLike<T> {
9+
#promise?: Promise<T>
10+
#executor: RawExecutor<T>
11+
12+
constructor(executor: RawExecutor<T>) {
13+
this.#executor = executor
14+
}
15+
16+
static from<T>(fn: Fn<never, Promisify<T>>) {
17+
return new Lazy(resolve => {
18+
resolve(fn())
19+
})
20+
}
21+
22+
// biome-ignore lint/suspicious/noThenProperty: Redundant
23+
get then() {
24+
if (!this.#promise) {
25+
this.#promise = new Promise<T>(this.#executor)
26+
}
27+
return this.#promise.then.bind(this.#promise)
28+
}
29+
30+
get catch() {
31+
if (!this.#promise) {
32+
this.#promise = new Promise<T>(this.#executor)
33+
}
34+
return this.#promise.catch.bind(this.#promise)
35+
}
36+
}

‎packages/core/tsconfig.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "../../tsconfig.base.json",
3+
"include": ["src", "test"],
4+
"references": [
5+
{
6+
"path": "../../tools/builder"
7+
}
8+
]
9+
}

‎tools/builder/moon.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
language: "typescript"
2-
type: "tool"
2+
type: "library"
33
platform: "bun"
44

55
tasks:

‎tools/builder/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
"bin": {
66
"builder": "./command.js"
77
},
8+
89
"main": "dist/index.js",
910
"module": "dist/index.js",
1011
"types": "dist/index.d.ts",
11-
"files": ["dist", "definitions", "./command.js"],
12+
"files": ["dist", "./command.js"],
13+
1214
"devDependencies": {},
1315
"dependencies": {
1416
"commander": "^12.0.0",

0 commit comments

Comments
 (0)