wellcrafted 0.34.1 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/error/index.d.ts +4 -113
- package/dist/error/index.js +3 -106
- package/dist/error-Dy9wXt5_.js +107 -0
- package/dist/error-Dy9wXt5_.js.map +1 -0
- package/dist/index-B9PnZCTt.d.ts +73 -0
- package/dist/index-B9PnZCTt.d.ts.map +1 -0
- package/dist/{index-D_iQ3bBj.d.ts → index-DnoV2ZDO.d.ts} +2 -2
- package/dist/{index-D_iQ3bBj.d.ts.map → index-DnoV2ZDO.d.ts.map} +1 -1
- package/dist/json.d.ts +57 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +51 -0
- package/dist/json.js.map +1 -0
- package/dist/logger/index.d.ts +228 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +173 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/query/index.d.ts +3 -2
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/index.js +3 -2
- package/dist/query/index.js.map +1 -1
- package/dist/result/index.d.ts +4 -3
- package/dist/result/index.js +4 -3
- package/dist/{result-BRfWC87j.js → result-C5cJ1_WU.js} +28 -7
- package/dist/result-C5cJ1_WU.js.map +1 -0
- package/dist/{result-Cd0chHlN.js → result-C9V2Knvt.js} +2 -2
- package/dist/{result-Cd0chHlN.js.map → result-C9V2Knvt.js.map} +1 -1
- package/dist/{result-xH3TbSDF.d.ts → result-DKwq9BCr.d.ts} +28 -7
- package/dist/result-DKwq9BCr.d.ts.map +1 -0
- package/dist/tap-err-CFhHBPfH.d.ts +33 -0
- package/dist/tap-err-CFhHBPfH.d.ts.map +1 -0
- package/dist/tap-err-CP-re1HT.js +37 -0
- package/dist/tap-err-CP-re1HT.js.map +1 -0
- package/dist/testing.d.ts +53 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +59 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-tXXk7K9Q.d.ts +47 -0
- package/dist/types-tXXk7K9Q.d.ts.map +1 -0
- package/package.json +9 -1
- package/dist/error/index.d.ts.map +0 -1
- package/dist/error/index.js.map +0 -1
- package/dist/result-BRfWC87j.js.map +0 -1
- package/dist/result-xH3TbSDF.d.ts.map +0 -1
package/dist/error/index.d.ts
CHANGED
|
@@ -1,113 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Base type for any tagged error, used as a minimum constraint.
|
|
7
|
-
*/
|
|
8
|
-
type AnyTaggedError = {
|
|
9
|
-
name: string;
|
|
10
|
-
message: string;
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* Constructor return must include `message: string`.
|
|
14
|
-
* JSON serializability is a convention, not enforced at the type level
|
|
15
|
-
* (optional fields produce `T | undefined` which breaks `JsonObject`).
|
|
16
|
-
*/
|
|
17
|
-
type ErrorBody = {
|
|
18
|
-
message: string;
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
|
-
* Per-key validation: tells the user exactly what `name` will be stamped as.
|
|
22
|
-
* If a user provides `name` in the return object, they see a descriptive error.
|
|
23
|
-
*/
|
|
24
|
-
type ValidateErrorBody<K extends string> = {
|
|
25
|
-
message: string;
|
|
26
|
-
name?: `The 'name' key is reserved as '${K}'. Remove it.`;
|
|
27
|
-
};
|
|
28
|
-
/** The config: each key is a variant name, each value is a constructor function. */
|
|
29
|
-
type ErrorsConfig = Record<string, (...args: any[]) => ErrorBody>;
|
|
30
|
-
/** Validates each config entry, injecting the key-specific `name` reservation message. */
|
|
31
|
-
type ValidatedConfig<T extends ErrorsConfig> = { [K in keyof T & string]: T[K] extends ((...args: infer A) => infer R) ? (...args: A) => R & ValidateErrorBody<K> : T[K] };
|
|
32
|
-
/** Single factory: takes constructor args, returns Err-wrapped error. */
|
|
33
|
-
type ErrorFactory<TName extends string, TFn extends (...args: any[]) => ErrorBody> = { [K in TName]: (...args: Parameters<TFn>) => Err<Readonly<{
|
|
34
|
-
name: TName;
|
|
35
|
-
} & ReturnType<TFn>>> };
|
|
36
|
-
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
|
37
|
-
/** Return type of `defineErrors`. Maps each config key to its factory. */
|
|
38
|
-
type DefineErrorsReturn<TConfig extends ErrorsConfig> = UnionToIntersection<{ [K in keyof TConfig & string]: ErrorFactory<K, TConfig[K]> }[keyof TConfig & string]>;
|
|
39
|
-
/** Extract the error type from a single factory. */
|
|
40
|
-
type InferError<T> = T extends ((...args: any[]) => Err<infer R>) ? R : never;
|
|
41
|
-
/** Extract union of ALL error types from a defineErrors return. */
|
|
42
|
-
type InferErrors<T> = { [K in keyof T]: T[K] extends ((...args: any[]) => Err<infer R>) ? R : never }[keyof T];
|
|
43
|
-
//#endregion
|
|
44
|
-
//#region src/error/defineErrors.d.ts
|
|
45
|
-
/**
|
|
46
|
-
* Defines a set of typed error factories using Rust-style namespaced variants.
|
|
47
|
-
*
|
|
48
|
-
* Each key is a short variant name (the namespace provides context). Every
|
|
49
|
-
* factory returns `Err<...>` directly — ready for `trySync`/`tryAsync` catch
|
|
50
|
-
* handlers. The variant name is stamped as `name` on the error object.
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```ts
|
|
54
|
-
* const HttpError = defineErrors({
|
|
55
|
-
* Connection: ({ cause }: { cause: unknown }) => ({
|
|
56
|
-
* message: `Failed to connect: ${extractErrorMessage(cause)}`,
|
|
57
|
-
* cause,
|
|
58
|
-
* }),
|
|
59
|
-
* Response: ({ status }: { status: number; bodyMessage?: string }) => ({
|
|
60
|
-
* message: `HTTP ${status}`,
|
|
61
|
-
* status,
|
|
62
|
-
* }),
|
|
63
|
-
* Parse: ({ cause }: { cause: unknown }) => ({
|
|
64
|
-
* message: `Failed to parse response body: ${extractErrorMessage(cause)}`,
|
|
65
|
-
* cause,
|
|
66
|
-
* }),
|
|
67
|
-
* });
|
|
68
|
-
*
|
|
69
|
-
* type HttpError = InferErrors<typeof HttpError>;
|
|
70
|
-
*
|
|
71
|
-
* const result = HttpError.Connection({ cause: 'timeout' }); // Err<...>
|
|
72
|
-
* ```
|
|
73
|
-
*
|
|
74
|
-
* Inspired by Rust's {@link https://docs.rs/thiserror | thiserror} crate. The
|
|
75
|
-
* mapping is nearly 1:1:
|
|
76
|
-
*
|
|
77
|
-
* - `enum HttpError` → `const HttpError = defineErrors(...)`
|
|
78
|
-
* - Variant `Connection { cause: String }` → key `Connection: ({ cause }: { cause: unknown }) => (...)`
|
|
79
|
-
* - `#[error("Failed: {cause}")]` → `` message: `Failed: ${extractErrorMessage(cause)}` ``
|
|
80
|
-
* - `HttpError::Connection { ... }` → `HttpError.Connection({ ... })`
|
|
81
|
-
* - `match error { Connection { .. } => }` → `switch (error.name) { case 'Connection': }`
|
|
82
|
-
*
|
|
83
|
-
* The equivalent Rust `thiserror` enum:
|
|
84
|
-
* ```rust
|
|
85
|
-
* #[derive(Error, Debug)]
|
|
86
|
-
* enum HttpError {
|
|
87
|
-
* #[error("Failed to connect: {cause}")]
|
|
88
|
-
* Connection { cause: String },
|
|
89
|
-
*
|
|
90
|
-
* #[error("HTTP {status}")]
|
|
91
|
-
* Response { status: u16, body_message: Option<String> },
|
|
92
|
-
*
|
|
93
|
-
* #[error("Failed to parse response body: {cause}")]
|
|
94
|
-
* Parse { cause: String },
|
|
95
|
-
* }
|
|
96
|
-
* ```
|
|
97
|
-
*/
|
|
98
|
-
declare function defineErrors<const TConfig extends ErrorsConfig>(config: TConfig & ValidatedConfig<TConfig>): DefineErrorsReturn<TConfig>;
|
|
99
|
-
//# sourceMappingURL=defineErrors.d.ts.map
|
|
100
|
-
//#endregion
|
|
101
|
-
//#region src/error/extractErrorMessage.d.ts
|
|
102
|
-
/**
|
|
103
|
-
* Extracts a readable error message from an unknown error value
|
|
104
|
-
*
|
|
105
|
-
* @param error - The unknown error to extract a message from
|
|
106
|
-
* @returns A string representation of the error
|
|
107
|
-
*/
|
|
108
|
-
declare function extractErrorMessage(error: unknown): string;
|
|
109
|
-
//# sourceMappingURL=extractErrorMessage.d.ts.map
|
|
110
|
-
|
|
111
|
-
//#endregion
|
|
112
|
-
export { AnyTaggedError, DefineErrorsReturn, ErrorBody, ErrorsConfig, InferError, InferErrors, ValidatedConfig, defineErrors, extractErrorMessage };
|
|
113
|
-
//# sourceMappingURL=index.d.ts.map
|
|
1
|
+
import "../result-DKwq9BCr.js";
|
|
2
|
+
import { AnyTaggedError, DefineErrorsReturn, ErrorBody, ErrorsConfig, InferError, InferErrors, ValidatedConfig } from "../types-tXXk7K9Q.js";
|
|
3
|
+
import { defineErrors, extractErrorMessage } from "../index-B9PnZCTt.js";
|
|
4
|
+
export { AnyTaggedError, DefineErrorsReturn, ErrorBody, ErrorsConfig, InferError, InferErrors, ValidatedConfig, defineErrors, extractErrorMessage };
|
package/dist/error/index.js
CHANGED
|
@@ -1,107 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "../result-C5cJ1_WU.js";
|
|
2
|
+
import { defineErrors, extractErrorMessage } from "../error-Dy9wXt5_.js";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Defines a set of typed error factories using Rust-style namespaced variants.
|
|
6
|
-
*
|
|
7
|
-
* Each key is a short variant name (the namespace provides context). Every
|
|
8
|
-
* factory returns `Err<...>` directly — ready for `trySync`/`tryAsync` catch
|
|
9
|
-
* handlers. The variant name is stamped as `name` on the error object.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```ts
|
|
13
|
-
* const HttpError = defineErrors({
|
|
14
|
-
* Connection: ({ cause }: { cause: unknown }) => ({
|
|
15
|
-
* message: `Failed to connect: ${extractErrorMessage(cause)}`,
|
|
16
|
-
* cause,
|
|
17
|
-
* }),
|
|
18
|
-
* Response: ({ status }: { status: number; bodyMessage?: string }) => ({
|
|
19
|
-
* message: `HTTP ${status}`,
|
|
20
|
-
* status,
|
|
21
|
-
* }),
|
|
22
|
-
* Parse: ({ cause }: { cause: unknown }) => ({
|
|
23
|
-
* message: `Failed to parse response body: ${extractErrorMessage(cause)}`,
|
|
24
|
-
* cause,
|
|
25
|
-
* }),
|
|
26
|
-
* });
|
|
27
|
-
*
|
|
28
|
-
* type HttpError = InferErrors<typeof HttpError>;
|
|
29
|
-
*
|
|
30
|
-
* const result = HttpError.Connection({ cause: 'timeout' }); // Err<...>
|
|
31
|
-
* ```
|
|
32
|
-
*
|
|
33
|
-
* Inspired by Rust's {@link https://docs.rs/thiserror | thiserror} crate. The
|
|
34
|
-
* mapping is nearly 1:1:
|
|
35
|
-
*
|
|
36
|
-
* - `enum HttpError` → `const HttpError = defineErrors(...)`
|
|
37
|
-
* - Variant `Connection { cause: String }` → key `Connection: ({ cause }: { cause: unknown }) => (...)`
|
|
38
|
-
* - `#[error("Failed: {cause}")]` → `` message: `Failed: ${extractErrorMessage(cause)}` ``
|
|
39
|
-
* - `HttpError::Connection { ... }` → `HttpError.Connection({ ... })`
|
|
40
|
-
* - `match error { Connection { .. } => }` → `switch (error.name) { case 'Connection': }`
|
|
41
|
-
*
|
|
42
|
-
* The equivalent Rust `thiserror` enum:
|
|
43
|
-
* ```rust
|
|
44
|
-
* #[derive(Error, Debug)]
|
|
45
|
-
* enum HttpError {
|
|
46
|
-
* #[error("Failed to connect: {cause}")]
|
|
47
|
-
* Connection { cause: String },
|
|
48
|
-
*
|
|
49
|
-
* #[error("HTTP {status}")]
|
|
50
|
-
* Response { status: u16, body_message: Option<String> },
|
|
51
|
-
*
|
|
52
|
-
* #[error("Failed to parse response body: {cause}")]
|
|
53
|
-
* Parse { cause: String },
|
|
54
|
-
* }
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
function defineErrors(config) {
|
|
58
|
-
const result = {};
|
|
59
|
-
for (const [name, ctor] of Object.entries(config)) result[name] = (...args) => {
|
|
60
|
-
const body = ctor(...args);
|
|
61
|
-
return Err(Object.freeze({
|
|
62
|
-
...body,
|
|
63
|
-
name
|
|
64
|
-
}));
|
|
65
|
-
};
|
|
66
|
-
return result;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
//#endregion
|
|
70
|
-
//#region src/error/extractErrorMessage.ts
|
|
71
|
-
/**
|
|
72
|
-
* Extracts a readable error message from an unknown error value
|
|
73
|
-
*
|
|
74
|
-
* @param error - The unknown error to extract a message from
|
|
75
|
-
* @returns A string representation of the error
|
|
76
|
-
*/
|
|
77
|
-
function extractErrorMessage(error) {
|
|
78
|
-
if (error instanceof Error) return error.message;
|
|
79
|
-
if (typeof error === "string") return error;
|
|
80
|
-
if (typeof error === "number" || typeof error === "boolean" || typeof error === "bigint") return String(error);
|
|
81
|
-
if (typeof error === "symbol") return error.toString();
|
|
82
|
-
if (error === null) return "null";
|
|
83
|
-
if (error === void 0) return "undefined";
|
|
84
|
-
if (Array.isArray(error)) return JSON.stringify(error);
|
|
85
|
-
if (typeof error === "object") {
|
|
86
|
-
const errorObj = error;
|
|
87
|
-
const messageProps = [
|
|
88
|
-
"message",
|
|
89
|
-
"error",
|
|
90
|
-
"description",
|
|
91
|
-
"title",
|
|
92
|
-
"reason",
|
|
93
|
-
"details"
|
|
94
|
-
];
|
|
95
|
-
for (const prop of messageProps) if (prop in errorObj && typeof errorObj[prop] === "string") return errorObj[prop];
|
|
96
|
-
try {
|
|
97
|
-
return JSON.stringify(error);
|
|
98
|
-
} catch {
|
|
99
|
-
return String(error);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return String(error);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
//#endregion
|
|
106
|
-
export { defineErrors, extractErrorMessage };
|
|
107
|
-
//# sourceMappingURL=index.js.map
|
|
4
|
+
export { defineErrors, extractErrorMessage };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Err } from "./result-C5cJ1_WU.js";
|
|
2
|
+
|
|
3
|
+
//#region src/error/defineErrors.ts
|
|
4
|
+
/**
|
|
5
|
+
* Defines a set of typed error factories using Rust-style namespaced variants.
|
|
6
|
+
*
|
|
7
|
+
* Each key is a short variant name (the namespace provides context). Every
|
|
8
|
+
* factory returns `Err<...>` directly — ready for `trySync`/`tryAsync` catch
|
|
9
|
+
* handlers. The variant name is stamped as `name` on the error object.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const HttpError = defineErrors({
|
|
14
|
+
* Connection: ({ cause }: { cause: unknown }) => ({
|
|
15
|
+
* message: `Failed to connect: ${extractErrorMessage(cause)}`,
|
|
16
|
+
* cause,
|
|
17
|
+
* }),
|
|
18
|
+
* Response: ({ status }: { status: number; bodyMessage?: string }) => ({
|
|
19
|
+
* message: `HTTP ${status}`,
|
|
20
|
+
* status,
|
|
21
|
+
* }),
|
|
22
|
+
* Parse: ({ cause }: { cause: unknown }) => ({
|
|
23
|
+
* message: `Failed to parse response body: ${extractErrorMessage(cause)}`,
|
|
24
|
+
* cause,
|
|
25
|
+
* }),
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* type HttpError = InferErrors<typeof HttpError>;
|
|
29
|
+
*
|
|
30
|
+
* const result = HttpError.Connection({ cause: 'timeout' }); // Err<...>
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Inspired by Rust's {@link https://docs.rs/thiserror | thiserror} crate. The
|
|
34
|
+
* mapping is nearly 1:1:
|
|
35
|
+
*
|
|
36
|
+
* - `enum HttpError` → `const HttpError = defineErrors(...)`
|
|
37
|
+
* - Variant `Connection { cause: String }` → key `Connection: ({ cause }: { cause: unknown }) => (...)`
|
|
38
|
+
* - `#[error("Failed: {cause}")]` → `` message: `Failed: ${extractErrorMessage(cause)}` ``
|
|
39
|
+
* - `HttpError::Connection { ... }` → `HttpError.Connection({ ... })`
|
|
40
|
+
* - `match error { Connection { .. } => }` → `switch (error.name) { case 'Connection': }`
|
|
41
|
+
*
|
|
42
|
+
* The equivalent Rust `thiserror` enum:
|
|
43
|
+
* ```rust
|
|
44
|
+
* #[derive(Error, Debug)]
|
|
45
|
+
* enum HttpError {
|
|
46
|
+
* #[error("Failed to connect: {cause}")]
|
|
47
|
+
* Connection { cause: String },
|
|
48
|
+
*
|
|
49
|
+
* #[error("HTTP {status}")]
|
|
50
|
+
* Response { status: u16, body_message: Option<String> },
|
|
51
|
+
*
|
|
52
|
+
* #[error("Failed to parse response body: {cause}")]
|
|
53
|
+
* Parse { cause: String },
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
function defineErrors(config) {
|
|
58
|
+
const result = {};
|
|
59
|
+
for (const [name, ctor] of Object.entries(config)) result[name] = (...args) => {
|
|
60
|
+
const body = ctor(...args);
|
|
61
|
+
return Err(Object.freeze({
|
|
62
|
+
...body,
|
|
63
|
+
name
|
|
64
|
+
}));
|
|
65
|
+
};
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/error/extractErrorMessage.ts
|
|
71
|
+
/**
|
|
72
|
+
* Extracts a readable error message from an unknown error value
|
|
73
|
+
*
|
|
74
|
+
* @param error - The unknown error to extract a message from
|
|
75
|
+
* @returns A string representation of the error
|
|
76
|
+
*/
|
|
77
|
+
function extractErrorMessage(error) {
|
|
78
|
+
if (error instanceof Error) return error.message;
|
|
79
|
+
if (typeof error === "string") return error;
|
|
80
|
+
if (typeof error === "number" || typeof error === "boolean" || typeof error === "bigint") return String(error);
|
|
81
|
+
if (typeof error === "symbol") return error.toString();
|
|
82
|
+
if (error === null) return "null";
|
|
83
|
+
if (error === void 0) return "undefined";
|
|
84
|
+
if (Array.isArray(error)) return JSON.stringify(error);
|
|
85
|
+
if (typeof error === "object") {
|
|
86
|
+
const errorObj = error;
|
|
87
|
+
const messageProps = [
|
|
88
|
+
"message",
|
|
89
|
+
"error",
|
|
90
|
+
"description",
|
|
91
|
+
"title",
|
|
92
|
+
"reason",
|
|
93
|
+
"details"
|
|
94
|
+
];
|
|
95
|
+
for (const prop of messageProps) if (prop in errorObj && typeof errorObj[prop] === "string") return errorObj[prop];
|
|
96
|
+
try {
|
|
97
|
+
return JSON.stringify(error);
|
|
98
|
+
} catch {
|
|
99
|
+
return String(error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return String(error);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
106
|
+
export { defineErrors, extractErrorMessage };
|
|
107
|
+
//# sourceMappingURL=error-Dy9wXt5_.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-Dy9wXt5_.js","names":["config: TConfig & ValidatedConfig<TConfig>","result: Record<string, unknown>","error: unknown"],"sources":["../src/error/defineErrors.ts","../src/error/extractErrorMessage.ts"],"sourcesContent":["import { Err } from \"../result/result.js\";\nimport type {\n\tDefineErrorsReturn,\n\tErrorsConfig,\n\tValidatedConfig,\n} from \"./types.js\";\n\n/**\n * Defines a set of typed error factories using Rust-style namespaced variants.\n *\n * Each key is a short variant name (the namespace provides context). Every\n * factory returns `Err<...>` directly — ready for `trySync`/`tryAsync` catch\n * handlers. The variant name is stamped as `name` on the error object.\n *\n * @example\n * ```ts\n * const HttpError = defineErrors({\n * Connection: ({ cause }: { cause: unknown }) => ({\n * message: `Failed to connect: ${extractErrorMessage(cause)}`,\n * cause,\n * }),\n * Response: ({ status }: { status: number; bodyMessage?: string }) => ({\n * message: `HTTP ${status}`,\n * status,\n * }),\n * Parse: ({ cause }: { cause: unknown }) => ({\n * message: `Failed to parse response body: ${extractErrorMessage(cause)}`,\n * cause,\n * }),\n * });\n *\n * type HttpError = InferErrors<typeof HttpError>;\n *\n * const result = HttpError.Connection({ cause: 'timeout' }); // Err<...>\n * ```\n *\n * Inspired by Rust's {@link https://docs.rs/thiserror | thiserror} crate. The\n * mapping is nearly 1:1:\n *\n * - `enum HttpError` → `const HttpError = defineErrors(...)`\n * - Variant `Connection { cause: String }` → key `Connection: ({ cause }: { cause: unknown }) => (...)`\n * - `#[error(\"Failed: {cause}\")]` → `` message: `Failed: ${extractErrorMessage(cause)}` ``\n * - `HttpError::Connection { ... }` → `HttpError.Connection({ ... })`\n * - `match error { Connection { .. } => }` → `switch (error.name) { case 'Connection': }`\n *\n * The equivalent Rust `thiserror` enum:\n * ```rust\n * #[derive(Error, Debug)]\n * enum HttpError {\n * #[error(\"Failed to connect: {cause}\")]\n * Connection { cause: String },\n *\n * #[error(\"HTTP {status}\")]\n * Response { status: u16, body_message: Option<String> },\n *\n * #[error(\"Failed to parse response body: {cause}\")]\n * Parse { cause: String },\n * }\n * ```\n */\nexport function defineErrors<const TConfig extends ErrorsConfig>(\n\tconfig: TConfig & ValidatedConfig<TConfig>,\n): DefineErrorsReturn<TConfig> {\n\tconst result: Record<string, unknown> = {};\n\n\tfor (const [name, ctor] of Object.entries(config)) {\n\t\tresult[name] = (...args: unknown[]) => {\n\t\t\tconst body = (ctor as (...a: unknown[]) => Record<string, unknown>)(\n\t\t\t\t...args,\n\t\t\t);\n\t\t\treturn Err(Object.freeze({ ...body, name }));\n\t\t};\n\t}\n\n\treturn result as DefineErrorsReturn<TConfig>;\n}\n","/**\n * Extracts a readable error message from an unknown error value\n *\n * @param error - The unknown error to extract a message from\n * @returns A string representation of the error\n */\nexport function extractErrorMessage(error: unknown): string {\n\t// Handle Error instances\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\n\t// Handle primitives\n\tif (typeof error === \"string\") return error;\n\tif (\n\t\ttypeof error === \"number\" ||\n\t\ttypeof error === \"boolean\" ||\n\t\ttypeof error === \"bigint\"\n\t)\n\t\treturn String(error);\n\tif (typeof error === \"symbol\") return error.toString();\n\tif (error === null) return \"null\";\n\tif (error === undefined) return \"undefined\";\n\n\t// Handle arrays\n\tif (Array.isArray(error)) return JSON.stringify(error);\n\n\t// Handle plain objects\n\tif (typeof error === \"object\") {\n\t\tconst errorObj = error as Record<string, unknown>;\n\n\t\t// Check common error properties\n\t\tconst messageProps = [\n\t\t\t\"message\",\n\t\t\t\"error\",\n\t\t\t\"description\",\n\t\t\t\"title\",\n\t\t\t\"reason\",\n\t\t\t\"details\",\n\t\t] as const;\n\t\tfor (const prop of messageProps) {\n\t\t\tif (prop in errorObj && typeof errorObj[prop] === \"string\") {\n\t\t\t\treturn errorObj[prop];\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to JSON stringification\n\t\ttry {\n\t\t\treturn JSON.stringify(error);\n\t\t} catch {\n\t\t\treturn String(error);\n\t\t}\n\t}\n\n\t// Final fallback\n\treturn String(error);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,SAAgB,aACfA,QAC8B;CAC9B,MAAMC,SAAkC,CAAE;AAE1C,MAAK,MAAM,CAAC,MAAM,KAAK,IAAI,OAAO,QAAQ,OAAO,CAChD,QAAO,QAAQ,CAAC,GAAG,SAAoB;EACtC,MAAM,OAAO,AAAC,KACb,GAAG,KACH;AACD,SAAO,IAAI,OAAO,OAAO;GAAE,GAAG;GAAM;EAAM,EAAC,CAAC;CAC5C;AAGF,QAAO;AACP;;;;;;;;;;ACrED,SAAgB,oBAAoBC,OAAwB;AAE3D,KAAI,iBAAiB,MACpB,QAAO,MAAM;AAId,YAAW,UAAU,SAAU,QAAO;AACtC,YACQ,UAAU,mBACV,UAAU,oBACV,UAAU,SAEjB,QAAO,OAAO,MAAM;AACrB,YAAW,UAAU,SAAU,QAAO,MAAM,UAAU;AACtD,KAAI,UAAU,KAAM,QAAO;AAC3B,KAAI,iBAAqB,QAAO;AAGhC,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,KAAK,UAAU,MAAM;AAGtD,YAAW,UAAU,UAAU;EAC9B,MAAM,WAAW;EAGjB,MAAM,eAAe;GACpB;GACA;GACA;GACA;GACA;GACA;EACA;AACD,OAAK,MAAM,QAAQ,aAClB,KAAI,QAAQ,mBAAmB,SAAS,UAAU,SACjD,QAAO,SAAS;AAKlB,MAAI;AACH,UAAO,KAAK,UAAU,MAAM;EAC5B,QAAO;AACP,UAAO,OAAO,MAAM;EACpB;CACD;AAGD,QAAO,OAAO,MAAM;AACpB"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { DefineErrorsReturn, ErrorsConfig, ValidatedConfig } from "./types-tXXk7K9Q.js";
|
|
2
|
+
|
|
3
|
+
//#region src/error/defineErrors.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Defines a set of typed error factories using Rust-style namespaced variants.
|
|
7
|
+
*
|
|
8
|
+
* Each key is a short variant name (the namespace provides context). Every
|
|
9
|
+
* factory returns `Err<...>` directly — ready for `trySync`/`tryAsync` catch
|
|
10
|
+
* handlers. The variant name is stamped as `name` on the error object.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const HttpError = defineErrors({
|
|
15
|
+
* Connection: ({ cause }: { cause: unknown }) => ({
|
|
16
|
+
* message: `Failed to connect: ${extractErrorMessage(cause)}`,
|
|
17
|
+
* cause,
|
|
18
|
+
* }),
|
|
19
|
+
* Response: ({ status }: { status: number; bodyMessage?: string }) => ({
|
|
20
|
+
* message: `HTTP ${status}`,
|
|
21
|
+
* status,
|
|
22
|
+
* }),
|
|
23
|
+
* Parse: ({ cause }: { cause: unknown }) => ({
|
|
24
|
+
* message: `Failed to parse response body: ${extractErrorMessage(cause)}`,
|
|
25
|
+
* cause,
|
|
26
|
+
* }),
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* type HttpError = InferErrors<typeof HttpError>;
|
|
30
|
+
*
|
|
31
|
+
* const result = HttpError.Connection({ cause: 'timeout' }); // Err<...>
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* Inspired by Rust's {@link https://docs.rs/thiserror | thiserror} crate. The
|
|
35
|
+
* mapping is nearly 1:1:
|
|
36
|
+
*
|
|
37
|
+
* - `enum HttpError` → `const HttpError = defineErrors(...)`
|
|
38
|
+
* - Variant `Connection { cause: String }` → key `Connection: ({ cause }: { cause: unknown }) => (...)`
|
|
39
|
+
* - `#[error("Failed: {cause}")]` → `` message: `Failed: ${extractErrorMessage(cause)}` ``
|
|
40
|
+
* - `HttpError::Connection { ... }` → `HttpError.Connection({ ... })`
|
|
41
|
+
* - `match error { Connection { .. } => }` → `switch (error.name) { case 'Connection': }`
|
|
42
|
+
*
|
|
43
|
+
* The equivalent Rust `thiserror` enum:
|
|
44
|
+
* ```rust
|
|
45
|
+
* #[derive(Error, Debug)]
|
|
46
|
+
* enum HttpError {
|
|
47
|
+
* #[error("Failed to connect: {cause}")]
|
|
48
|
+
* Connection { cause: String },
|
|
49
|
+
*
|
|
50
|
+
* #[error("HTTP {status}")]
|
|
51
|
+
* Response { status: u16, body_message: Option<String> },
|
|
52
|
+
*
|
|
53
|
+
* #[error("Failed to parse response body: {cause}")]
|
|
54
|
+
* Parse { cause: String },
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
declare function defineErrors<const TConfig extends ErrorsConfig>(config: TConfig & ValidatedConfig<TConfig>): DefineErrorsReturn<TConfig>;
|
|
59
|
+
//# sourceMappingURL=defineErrors.d.ts.map
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/error/extractErrorMessage.d.ts
|
|
62
|
+
/**
|
|
63
|
+
* Extracts a readable error message from an unknown error value
|
|
64
|
+
*
|
|
65
|
+
* @param error - The unknown error to extract a message from
|
|
66
|
+
* @returns A string representation of the error
|
|
67
|
+
*/
|
|
68
|
+
declare function extractErrorMessage(error: unknown): string;
|
|
69
|
+
//# sourceMappingURL=extractErrorMessage.d.ts.map
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
export { defineErrors, extractErrorMessage };
|
|
73
|
+
//# sourceMappingURL=index-B9PnZCTt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-B9PnZCTt.d.ts","names":[],"sources":["../src/error/defineErrors.ts","../src/error/extractErrorMessage.ts"],"sourcesContent":[],"mappings":";;;;;;AA4DA;;;;;;;;AAEqB;;;;ACxDrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBDsDgB,mCAAmC,sBAC1C,UAAU,gBAAgB,WAChC,mBAAmB;;;;;;;AAFtB;;;AACS,iBCvDO,mBAAA,CDuDP,KAAA,EAAA,OAAA,CAAA,EAAA,MAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Err, Ok, Result } from "./result-
|
|
1
|
+
import { Err, Ok, Result } from "./result-DKwq9BCr.js";
|
|
2
2
|
|
|
3
3
|
//#region src/result/utils.d.ts
|
|
4
4
|
|
|
@@ -25,4 +25,4 @@ declare function partitionResults<T, E>(results: Result<T, E>[]): {
|
|
|
25
25
|
//# sourceMappingURL=utils.d.ts.map
|
|
26
26
|
//#endregion
|
|
27
27
|
export { partitionResults };
|
|
28
|
-
//# sourceMappingURL=index-
|
|
28
|
+
//# sourceMappingURL=index-DnoV2ZDO.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index-
|
|
1
|
+
{"version":3,"file":"index-DnoV2ZDO.d.ts","names":[],"sources":["../src/result/utils.ts"],"sourcesContent":[],"mappings":";;;;;;AAmBA;;;;;;;;;AAK+B;;;;;iBALf,gCAAgC,OAAO,GAAG;OAK7C,GAAG;QAAY,IAAI"}
|
package/dist/json.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
import { Err, Result } from "./result-DKwq9BCr.js";
|
|
2
|
+
import { InferError } from "./types-tXXk7K9Q.js";
|
|
3
|
+
import "./index-B9PnZCTt.js";
|
|
4
|
+
import "./tap-err-CFhHBPfH.js";
|
|
5
|
+
import "./index-DnoV2ZDO.js";
|
|
6
|
+
|
|
1
7
|
//#region src/json.d.ts
|
|
8
|
+
|
|
2
9
|
/**
|
|
3
10
|
* JSON-serializable value types.
|
|
4
11
|
* Ensures data can be safely serialized via JSON.stringify.
|
|
@@ -19,7 +26,56 @@ type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
|
19
26
|
* A record where every value is a {@link JsonValue}.
|
|
20
27
|
*/
|
|
21
28
|
type JsonObject = Record<string, JsonValue>;
|
|
29
|
+
/**
|
|
30
|
+
* Constructs a {@link JsonParseError}. Returns `Err<JsonParseError>` directly,
|
|
31
|
+
* ready to return from a `trySync`/`tryAsync` catch handler.
|
|
32
|
+
*/
|
|
33
|
+
declare const JsonParseError: (args_0: {
|
|
34
|
+
cause: unknown;
|
|
35
|
+
}) => Err<Readonly<{
|
|
36
|
+
name: "JsonParseError";
|
|
37
|
+
} & {
|
|
38
|
+
message: string;
|
|
39
|
+
cause: unknown;
|
|
40
|
+
}>>;
|
|
41
|
+
/**
|
|
42
|
+
* The error {@link parseJson} produces when its input is not valid JSON.
|
|
43
|
+
*
|
|
44
|
+
* JSON parsing has exactly one failure mode (the engine throws a `SyntaxError`),
|
|
45
|
+
* so this is a single variant. A valid-JSON-but-wrong-shape value is not a
|
|
46
|
+
* parse failure: validate the returned {@link JsonValue} against a schema for
|
|
47
|
+
* that case.
|
|
48
|
+
*/
|
|
49
|
+
type JsonParseError = InferError<typeof JsonParseError>;
|
|
50
|
+
/**
|
|
51
|
+
* Parses a JSON string into a {@link JsonValue}, returning a `Result` instead
|
|
52
|
+
* of throwing.
|
|
53
|
+
*
|
|
54
|
+
* Unlike `JSON.parse`, which returns `any` and throws on malformed input, this:
|
|
55
|
+
* - types the success value as {@link JsonValue}, forcing you to narrow or
|
|
56
|
+
* validate before treating it as a known shape
|
|
57
|
+
* - reports failure as a tagged {@link JsonParseError} rather than an exception
|
|
58
|
+
*
|
|
59
|
+
* No reviver argument is accepted. A reviver can return arbitrary values, which
|
|
60
|
+
* would make the {@link JsonValue} success type a lie.
|
|
61
|
+
*
|
|
62
|
+
* @param text - The JSON string to parse.
|
|
63
|
+
* @returns `Ok<JsonValue>` on success, `Err<JsonParseError>` on malformed input.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* import { parseJson } from "wellcrafted/json";
|
|
68
|
+
*
|
|
69
|
+
* const { data, error } = parseJson('{"count":1}');
|
|
70
|
+
* if (error) {
|
|
71
|
+
* console.error(error.message); // "Failed to parse JSON: ..."
|
|
72
|
+
* } else {
|
|
73
|
+
* data; // JsonValue — narrow or validate before using
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
declare function parseJson(text: string): Result<JsonValue, JsonParseError>;
|
|
22
78
|
//# sourceMappingURL=json.d.ts.map
|
|
23
79
|
//#endregion
|
|
24
|
-
export { JsonObject, JsonValue };
|
|
80
|
+
export { JsonObject, JsonParseError, JsonValue, parseJson };
|
|
25
81
|
//# sourceMappingURL=json.d.ts.map
|
package/dist/json.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json.d.ts","names":[],"sources":["../src/json.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"json.d.ts","names":[],"sources":["../src/json.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AAmBA;;;;AAM6B;AAM7B;;AAAwC,KAZ5B,SAAA,GAY4B,MAAA,GAAA,MAAA,GAAA,OAAA,GAAA,IAAA,GAPrC,SAOqC,EAAA,GAAA;EAAS,CAAA,GAAxB,EAAA,MAAA,CAAA,EANL,SAMK;AAAM,CAAA;AAU/B;;;;KAVY,UAAA,GAAa,eAAe;AAyBxC;;;;AAAuC,cAfxB,cAewB,EAAA,CAAA,MAAA,EAAA;EA6BvB,KAAA,EAAA,OAAS;CAAA,EAAA,MAAA,CAvCvB,QAuCuB,CAAA;EAAA,IAAuB,EAAA,gBAAA;CAAS,GAAA;EAAgB,OAAhC,EAAA,MAAA;EAAM,KAAA,EAAA,OAAA;;;;;;;;;;KA7BnC,cAAA,GAAiB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6B/B,SAAA,gBAAyB,OAAO,WAAW"}
|
package/dist/json.js
CHANGED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { trySync } from "./result-C5cJ1_WU.js";
|
|
2
|
+
import { defineErrors, extractErrorMessage } from "./error-Dy9wXt5_.js";
|
|
3
|
+
import "./tap-err-CP-re1HT.js";
|
|
4
|
+
import "./result-C9V2Knvt.js";
|
|
5
|
+
|
|
6
|
+
//#region src/json.ts
|
|
7
|
+
/**
|
|
8
|
+
* Constructs a {@link JsonParseError}. Returns `Err<JsonParseError>` directly,
|
|
9
|
+
* ready to return from a `trySync`/`tryAsync` catch handler.
|
|
10
|
+
*/
|
|
11
|
+
const { JsonParseError } = defineErrors({ JsonParseError: ({ cause }) => ({
|
|
12
|
+
message: `Failed to parse JSON: ${extractErrorMessage(cause)}`,
|
|
13
|
+
cause
|
|
14
|
+
}) });
|
|
15
|
+
/**
|
|
16
|
+
* Parses a JSON string into a {@link JsonValue}, returning a `Result` instead
|
|
17
|
+
* of throwing.
|
|
18
|
+
*
|
|
19
|
+
* Unlike `JSON.parse`, which returns `any` and throws on malformed input, this:
|
|
20
|
+
* - types the success value as {@link JsonValue}, forcing you to narrow or
|
|
21
|
+
* validate before treating it as a known shape
|
|
22
|
+
* - reports failure as a tagged {@link JsonParseError} rather than an exception
|
|
23
|
+
*
|
|
24
|
+
* No reviver argument is accepted. A reviver can return arbitrary values, which
|
|
25
|
+
* would make the {@link JsonValue} success type a lie.
|
|
26
|
+
*
|
|
27
|
+
* @param text - The JSON string to parse.
|
|
28
|
+
* @returns `Ok<JsonValue>` on success, `Err<JsonParseError>` on malformed input.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { parseJson } from "wellcrafted/json";
|
|
33
|
+
*
|
|
34
|
+
* const { data, error } = parseJson('{"count":1}');
|
|
35
|
+
* if (error) {
|
|
36
|
+
* console.error(error.message); // "Failed to parse JSON: ..."
|
|
37
|
+
* } else {
|
|
38
|
+
* data; // JsonValue — narrow or validate before using
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
function parseJson(text) {
|
|
43
|
+
return trySync({
|
|
44
|
+
try: () => JSON.parse(text),
|
|
45
|
+
catch: (cause) => JsonParseError({ cause })
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { JsonParseError, parseJson };
|
|
51
|
+
//# sourceMappingURL=json.js.map
|
package/dist/json.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","names":["text: string"],"sources":["../src/json.ts"],"sourcesContent":["import {\n\tdefineErrors,\n\textractErrorMessage,\n\ttype InferError,\n} from \"./error/index.js\";\nimport { type Result, trySync } from \"./result/index.js\";\n\n/**\n * JSON-serializable value types.\n * Ensures data can be safely serialized via JSON.stringify.\n *\n * @example\n * ```typescript\n * import type { JsonValue, JsonObject } from \"wellcrafted/json\";\n *\n * const value: JsonValue = { key: [1, \"two\", true, null] };\n * const obj: JsonObject = { name: \"Alice\", age: 30 };\n * ```\n */\nexport type JsonValue =\n\t| string\n\t| number\n\t| boolean\n\t| null\n\t| JsonValue[]\n\t| { [key: string]: JsonValue };\n\n/**\n * JSON-serializable object type.\n * A record where every value is a {@link JsonValue}.\n */\nexport type JsonObject = Record<string, JsonValue>;\n\n// =============================================================================\n// Parsing\n// =============================================================================\n\n/**\n * Constructs a {@link JsonParseError}. Returns `Err<JsonParseError>` directly,\n * ready to return from a `trySync`/`tryAsync` catch handler.\n */\nexport const { JsonParseError } = defineErrors({\n\tJsonParseError: ({ cause }: { cause: unknown }) => ({\n\t\tmessage: `Failed to parse JSON: ${extractErrorMessage(cause)}`,\n\t\tcause,\n\t}),\n});\n\n/**\n * The error {@link parseJson} produces when its input is not valid JSON.\n *\n * JSON parsing has exactly one failure mode (the engine throws a `SyntaxError`),\n * so this is a single variant. A valid-JSON-but-wrong-shape value is not a\n * parse failure: validate the returned {@link JsonValue} against a schema for\n * that case.\n */\nexport type JsonParseError = InferError<typeof JsonParseError>;\n\n/**\n * Parses a JSON string into a {@link JsonValue}, returning a `Result` instead\n * of throwing.\n *\n * Unlike `JSON.parse`, which returns `any` and throws on malformed input, this:\n * - types the success value as {@link JsonValue}, forcing you to narrow or\n * validate before treating it as a known shape\n * - reports failure as a tagged {@link JsonParseError} rather than an exception\n *\n * No reviver argument is accepted. A reviver can return arbitrary values, which\n * would make the {@link JsonValue} success type a lie.\n *\n * @param text - The JSON string to parse.\n * @returns `Ok<JsonValue>` on success, `Err<JsonParseError>` on malformed input.\n *\n * @example\n * ```ts\n * import { parseJson } from \"wellcrafted/json\";\n *\n * const { data, error } = parseJson('{\"count\":1}');\n * if (error) {\n * console.error(error.message); // \"Failed to parse JSON: ...\"\n * } else {\n * data; // JsonValue — narrow or validate before using\n * }\n * ```\n */\nexport function parseJson(text: string): Result<JsonValue, JsonParseError> {\n\treturn trySync({\n\t\ttry: () => JSON.parse(text) as JsonValue,\n\t\tcatch: (cause) => JsonParseError({ cause }),\n\t});\n}\n"],"mappings":";;;;;;;;;;AAyCA,MAAa,EAAE,gBAAgB,GAAG,aAAa,EAC9C,gBAAgB,CAAC,EAAE,OAA2B,MAAM;CACnD,SAAS,CAAC,sBAAsB,EAAE,oBAAoB,MAAM,EAAE;CAC9D;AACA,GACD,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCF,SAAgB,UAAUA,MAAiD;AAC1E,QAAO,QAAQ;EACd,KAAK,MAAM,KAAK,MAAM,KAAK;EAC3B,OAAO,CAAC,UAAU,eAAe,EAAE,MAAO,EAAC;CAC3C,EAAC;AACF"}
|